Merge mozilla-central to mozilla-inbound. CLOSED TREE
authorDorel Luca <dluca@mozilla.com>
Wed, 16 Jan 2019 00:25:16 +0200
changeset 511132 62ce57cf81571a894c42c91199aa1379d4a15e5b
parent 511131 70f87448b9e46e5d4ca7c2965d210ec2f1d5acd3 (current diff)
parent 511059 2bf2c209f520c75405b70ed5a5a12c2a8938dffe (diff)
child 511133 39346b9630dec5b3b076a7c8a5b8dc0e88eb5dea
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. CLOSED TREE
browser/components/preferences/in-content/tests/browser.ini
browser/components/preferences/in-content/tests/browser_privacypane.js
browser/docs/BrowserErrorReporter.rst
browser/modules/BrowserErrorReporter.jsm
browser/modules/test/browser/browser_BrowserErrorReporter.html
browser/modules/test/browser/browser_BrowserErrorReporter.js
browser/modules/test/browser/browser_BrowserErrorReporter_nightly.js
browser/modules/test/browser/head_BrowserErrorReporter.js
other-licenses/7zstub/firefox/7zSD.manifest
other-licenses/7zstub/firefox/7zSD.sfx
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1730,29 +1730,16 @@ pref("browser.sessionstore.restore_tabs_
 pref("browser.suppress_first_window_animation", true);
 
 // Preference that allows individual users to disable Screenshots.
 pref("extensions.screenshots.disabled", false);
 // Preference that allows individual users to leave Screenshots enabled, but
 // disable uploading to the server.
 pref("extensions.screenshots.upload-disabled", false);
 
-// Preferences for BrowserErrorReporter.jsm
-// Only collect errors on Nightly, and specifically not local builds
-#if defined(NIGHTLY_BUILD) && MOZ_UPDATE_CHANNEL != default
-pref("browser.chrome.errorReporter.enabled", true);
-#else
-pref("browser.chrome.errorReporter.enabled", false);
-#endif
-pref("browser.chrome.errorReporter.sampleRate", "0.001");
-pref("browser.chrome.errorReporter.publicKey", "c709cb7a2c0b4f0882fcc84a5af161ec");
-pref("browser.chrome.errorReporter.projectId", "339");
-pref("browser.chrome.errorReporter.submitUrl", "https://sentry.prod.mozaws.net/api/339/store/");
-pref("browser.chrome.errorReporter.logLevel", "Error");
-
 // URL for Learn More link for browser error logging in preferences
 pref("browser.chrome.errorReporter.infoURL",
      "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/nightly-error-collection");
 
 // Normandy client preferences
 pref("app.normandy.api_url", "https://normandy.cdn.mozilla.net/api/v1");
 pref("app.normandy.dev_mode", false);
 pref("app.normandy.enabled", true);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -379,17 +379,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AddonManager: "resource://gre/modules/AddonManager.jsm",
   AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.jsm",
   AsyncPrefs: "resource://gre/modules/AsyncPrefs.jsm",
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
   AutoCompletePopup: "resource://gre/modules/AutoCompletePopup.jsm",
   Blocklist: "resource://gre/modules/Blocklist.jsm",
   BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.jsm",
   BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.jsm",
-  BrowserErrorReporter: "resource:///modules/BrowserErrorReporter.jsm",
   BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   ContentClick: "resource:///modules/ContentClick.jsm",
   ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
   CustomizableUI: "resource:///modules/CustomizableUI.jsm",
   DateTimePickerParent: "resource://gre/modules/DateTimePickerParent.jsm",
   Discovery: "resource:///modules/Discovery.jsm",
   ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
@@ -653,26 +652,16 @@ BrowserGlue.prototype = {
   get pingCentre() {
     const MAIN_TOPIC_ID = "main";
     Object.defineProperty(this, "pingCentre", {
       value: new PingCentre({ topic: MAIN_TOPIC_ID }),
     });
     return this.pingCentre;
   },
 
-  /**
-   * Lazily initialize BrowserErrorReporter
-   */
-  get browserErrorReporter() {
-    Object.defineProperty(this, "browserErrorReporter", {
-      value: new BrowserErrorReporter(),
-    });
-    return this.browserErrorReporter;
-  },
-
   _sendMainPingCentrePing() {
     let newTabSetting;
     let homePageSetting;
 
     // Check whether or not about:home and about:newtab have been overridden at this point.
     // Different settings are encoded as follows:
     //   * Value 0: default
     //   * Value 1: about:blank
@@ -1450,22 +1439,16 @@ BrowserGlue.prototype = {
     }
 
     PageThumbs.uninit();
     NewTabUtils.uninit();
     AboutPrivateBrowsingHandler.uninit();
     AutoCompletePopup.uninit();
     DateTimePickerParent.uninit();
 
-    // Browser errors are only collected on Nightly, but telemetry for
-    // them is collected on all channels.
-    if (AppConstants.MOZ_DATA_REPORTING) {
-      this.browserErrorReporter.uninit();
-    }
-
     Normandy.uninit();
   },
 
   // Set up a listener to enable/disable the screenshots extension
   // based on its preference.
   _monitorScreenshotsPref() {
     const PREF = "extensions.screenshots.disabled";
     const ID = "screenshots@mozilla.org";
@@ -1498,22 +1481,16 @@ BrowserGlue.prototype = {
 
   // All initial windows have opened.
   _onWindowsRestored: function BG__onWindowsRestored() {
     if (this._windowsWereRestored) {
       return;
     }
     this._windowsWereRestored = true;
 
-    // Browser errors are only collected on Nightly, but telemetry for
-    // them is collected on all channels.
-    if (AppConstants.MOZ_DATA_REPORTING) {
-      this.browserErrorReporter.init();
-    }
-
     BrowserUsageTelemetry.init();
     SearchTelemetry.init();
 
     // Show update notification, if needed.
     if (Services.prefs.prefHasUserValue("app.update.postupdate"))
       this._showUpdateNotification();
 
     ExtensionsUI.init();
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -125,19 +125,16 @@ if (AppConstants.MOZ_DATA_REPORTING) {
     // Preference instances for prefs that we need to monitor while the page is open.
     { id: PREF_OPT_OUT_STUDIES_ENABLED, type: "bool" },
     { id: PREF_ADDON_RECOMMENDATIONS_ENABLED, type: "bool" },
     { id: PREF_UPLOAD_ENABLED, type: "bool" },
   ]);
 }
 
 // Data Choices tab
-if (AppConstants.NIGHTLY_BUILD) {
-  Preferences.add({ id: "browser.chrome.errorReporter.enabled", type: "bool" });
-}
 if (AppConstants.MOZ_CRASHREPORTER) {
   Preferences.add({ id: "browser.crashReports.unsubmittedCheck.autoSubmit2", type: "bool" });
 }
 
 function setEventListener(aId, aEventType, aCallback) {
   document.getElementById(aId)
     .addEventListener(aEventType, aCallback.bind(gPrivacyPane));
 }
@@ -401,19 +398,16 @@ var gPrivacyPane = {
     if (!emeUIEnabled) {
       // Don't want to rely on .hidden for the toplevel groupbox because
       // of the pane hiding/showing code potentially interfering:
       document.getElementById("drmGroup").setAttribute("style", "display: none !important");
     }
 
     if (AppConstants.MOZ_DATA_REPORTING) {
       this.initDataCollection();
-      if (AppConstants.NIGHTLY_BUILD) {
-        this.initCollectBrowserErrors();
-      }
       if (AppConstants.MOZ_CRASHREPORTER) {
         this.initSubmitCrashes();
       }
       this.initSubmitHealthReport();
       setEventListener("submitHealthReportBox", "command",
         gPrivacyPane.updateSubmitHealthReport);
       this.initOptOutStudyCheckbox();
       this.initAddonRecommendationsCheckbox();
@@ -1480,21 +1474,16 @@ var gPrivacyPane = {
     gSubDialog.open("chrome://pippki/content/device_manager.xul");
   },
 
   initDataCollection() {
     this._setupLearnMoreLink("toolkit.datacollection.infoURL",
       "dataCollectionPrivacyNotice");
   },
 
-  initCollectBrowserErrors() {
-    this._setupLearnMoreLink("browser.chrome.errorReporter.infoURL",
-      "collectBrowserErrorsLearnMore");
-  },
-
   initSubmitCrashes() {
     this._setupLearnMoreLink("toolkit.crashreporter.infoURL",
       "crashReporterLearnMore");
   },
 
   /**
    * Set up or hide the Learn More links for various data collection options
    */
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -682,28 +682,16 @@
       </vbox>
     </description>
 #ifndef MOZ_TELEMETRY_REPORTING
   <description id="TelemetryDisabledDesc"
     class="indent tip-caption" control="telemetryGroup"
     data-l10n-id="collection-health-report-disabled"/>
 #endif
 
-#ifdef NIGHTLY_BUILD
-    <hbox align="center">
-      <checkbox id="collectBrowserErrorsBox"
-                class="tail-with-learn-more"
-                preference="browser.chrome.errorReporter.enabled"
-                data-l10n-id="collection-browser-errors"
-                flex="1"/>
-      <label id="collectBrowserErrorsLearnMore"
-             class="learnMore text-link" data-l10n-id="collection-browser-errors-link"/>
-    </hbox>
-#endif
-
 #ifdef MOZ_CRASHREPORTER
     <hbox align="center">
       <checkbox id="automaticallySubmitCrashesBox"
                 class="tail-with-learn-more"
                 preference="browser.crashReports.unsubmittedCheck.autoSubmit2"
                 data-l10n-id="collection-backlogged-crash-reports"
                 flex="1"/>
       <label id="crashReporterLearnMore"
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -65,21 +65,16 @@ skip-if = ccov && os == 'win' # bug 1437
 [browser_performance.js]
 skip-if = !e10s
 [browser_performance_e10srollout.js]
 skip-if = !e10s
 [browser_performance_non_e10s.js]
 skip-if = e10s
 [browser_permissions_urlFieldHidden.js]
 [browser_proxy_backup.js]
-[browser_privacypane.js]
-run-if = nightly_build
-# browser_privacypane.js only has Browser Error collection tests currently,
-# which is disabled outside Nightly. Remove this once non-Nightly tests are
-# added.
 [browser_privacypane_2.js]
 [browser_privacypane_3.js]
 [browser_sanitizeOnShutdown_prefLocked.js]
 [browser_searchShowSuggestionsFirst.js]
 [browser_searchsuggestions.js]
 [browser_security-1.js]
 [browser_security-2.js]
 [browser_spotlight.js]
deleted file mode 100644
--- a/browser/components/preferences/in-content/tests/browser_privacypane.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Test the initial value of Browser Error collection checkbox
-add_task(async function testBrowserErrorInitialValue() {
-  // Skip if non-Nightly since the checkbox will be missing.
-  if (!AppConstants.NIGHTLY_BUILD) {
-    return;
-  }
-
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.chrome.errorReporter.enabled", true]],
-  });
-  await openPreferencesViaOpenPreferencesAPI("privacy-reports", {leaveOpen: true});
-
-  let doc = gBrowser.contentDocument;
-  ok(
-    doc.querySelector("#collectBrowserErrorsBox").checked,
-    "Checkbox for collecting browser errors should be checked when the pref is true"
-  );
-
-  BrowserTestUtils.removeTab(gBrowser.selectedTab);
-  await SpecialPowers.popPrefEnv();
-});
-
-// Test that the Learn More link is set to the correct, formatted URL from a
-// pref value
-add_task(async function testBrowserErrorLearnMore() {
-  // Skip if non-Nightly since the checkbox will be missing.
-  if (!AppConstants.NIGHTLY_BUILD) {
-    return;
-  }
-
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.chrome.errorReporter.infoURL", "https://example.com/%NAME%/"]],
-  });
-  await openPreferencesViaOpenPreferencesAPI("privacy-reports", {leaveOpen: true});
-
-  let doc = gBrowser.contentDocument;
-  is(
-    doc.querySelector("#collectBrowserErrorsLearnMore").href,
-    `https://example.com/${Services.appinfo.name}/`,
-    "Learn More link for browser error collection should have an href set by a pref"
-  );
-
-  BrowserTestUtils.removeTab(gBrowser.selectedTab);
-  await SpecialPowers.popPrefEnv();
-});
deleted file mode 100644
--- a/browser/docs/BrowserErrorReporter.rst
+++ /dev/null
@@ -1,48 +0,0 @@
-.. _browsererrorreporter:
-
-=======================
-Browser Error Reporter
-=======================
-
-The `BrowserErrorReporter.jsm <https://dxr.mozilla.org/mozilla-central/source/browser/modules/BrowserErrorReporter.jsm>`_ module collects errors logged to the Browser Console and sends them to a remote error aggregation service.
-
-.. note::
-   This module and the related service is a prototype and will be removed from Firefox in later 2018.
-
-Opt-out
-=======
-Collection is enabled by default in the Nightly channel, except for local builds, where it is disabled. It is not available outside of the Nightly channel.
-
-To opt-out of collection:
-
-1. Open ``about:preferences``.
-2. Select the Privacy and Security panel and go to the Nightly Data Collection and Use section.
-3. Uncheck "Allow Nightly to send browser error reports (including error messages) to Mozilla".
-
-Collected Error Data
-====================
-Errors are first sampled at the rate specified by the ``browser.chrome.errorReporter.sampleRate`` preference.
-
-The payload sent to the remote collection service contains the following info:
-
-- Firefox version number
-- Firefox update channel (usually "Nightly")
-- Firefox build ID
-- Revision used to build Firefox
-- Timestamp when the error occurred
-- A project ID specified by the ``browser.chrome.errorReporter.projectId`` preference
-- The error message
-- The filename that the error was thrown from
-- A stacktrace, if available, of the code being executed when the error was thrown
-
-Privacy-sensitive info
-======================
-Error reports may contain sensitive information about the user:
-
-- Error messages may inadvertently contain personal info depending on the code that generated the error.
-- Filenames in the stack trace may contain add-on IDs of currently-installed add-ons. They may also contain local filesystem paths.
-
-.. seealso::
-
-   `Browser Error Collection wiki page <https://wiki.mozilla.org/Firefox/BrowserErrorCollection>`_
-      Wiki page with up-to-date information on error collection and how we restrict access to the collected data.
--- a/browser/docs/index.rst
+++ b/browser/docs/index.rst
@@ -4,9 +4,8 @@ Firefox
 
 This is the nascent documentation of the Firefox front-end code.
 
 .. toctree::
    :maxdepth: 2
 
    AddressBar
    BrowserUsageTelemetry
-   BrowserErrorReporter
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -945,21 +945,16 @@ collection-studies-link = View { -brand-
 addon-recommendations =
     .label = Allow { -brand-short-name } to make personalized extension recommendations
 addon-recommendations-link = Learn more
 
 # This message is displayed above disabled data sharing options in developer builds
 # or builds with no Telemetry support available.
 collection-health-report-disabled = Data reporting is disabled for this build configuration
 
-collection-browser-errors =
-    .label = Allow { -brand-short-name } to send browser error reports (including error messages) to { -vendor-short-name }
-    .accesskey = b
-collection-browser-errors-link = Learn more
-
 collection-backlogged-crash-reports =
     .label = Allow { -brand-short-name } to send backlogged crash reports on your behalf
     .accesskey = c
 collection-backlogged-crash-reports-link = Learn more
 
 ## Privacy Section - Security
 ##
 ## It is important that wording follows the guidelines outlined on this page:
deleted file mode 100644
--- a/browser/modules/BrowserErrorReporter.jsm
+++ /dev/null
@@ -1,474 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/Timer.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-ChromeUtils.defineModuleGetter(this, "Log", "resource://gre/modules/Log.jsm");
-ChromeUtils.defineModuleGetter(this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm");
-
-XPCOMUtils.defineLazyGlobalGetters(this, ["fetch", "URL"]);
-
-var EXPORTED_SYMBOLS = ["BrowserErrorReporter"];
-
-const CONTEXT_LINES = 5;
-const ERROR_PREFIX_RE = /^[^\W]+:/m;
-const PREF_ENABLED = "browser.chrome.errorReporter.enabled";
-const PREF_LOG_LEVEL = "browser.chrome.errorReporter.logLevel";
-const PREF_PROJECT_ID = "browser.chrome.errorReporter.projectId";
-const PREF_PUBLIC_KEY = "browser.chrome.errorReporter.publicKey";
-const PREF_SAMPLE_RATE = "browser.chrome.errorReporter.sampleRate";
-const PREF_SUBMIT_URL = "browser.chrome.errorReporter.submitUrl";
-const RECENT_BUILD_AGE = 1000 * 60 * 60 * 24 * 7; // 7 days
-const SDK_NAME = "firefox-error-reporter";
-const SDK_VERSION = "1.0.0";
-const TELEMETRY_ERROR_COLLECTED = "browser.errors.collected_count";
-const TELEMETRY_ERROR_COLLECTED_FILENAME = "browser.errors.collected_count_by_filename";
-const TELEMETRY_ERROR_COLLECTED_STACK = "browser.errors.collected_with_stack_count";
-const TELEMETRY_ERROR_REPORTED = "browser.errors.reported_success_count";
-const TELEMETRY_ERROR_REPORTED_FAIL = "browser.errors.reported_failure_count";
-const TELEMETRY_ERROR_SAMPLE_RATE = "browser.errors.sample_rate";
-
-
-// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIScriptError#Categories
-const REPORTED_CATEGORIES = new Set([
-  "XPConnect JavaScript",
-  "component javascript",
-  "chrome javascript",
-  "chrome registration",
-  "XBL",
-  "XBL Prototype Handler",
-  "XBL Content Sink",
-  "xbl javascript",
-  "FrameConstructor",
-]);
-
-const PLATFORM_NAMES = {
-  linux: "Linux",
-  win: "Windows",
-  macosx: "macOS",
-  android: "Android",
-};
-
-// Filename URI regexes that we are okay with reporting to Telemetry. URIs not
-// matching these patterns may contain local file paths.
-const TELEMETRY_REPORTED_PATTERNS = new Set([
-  /^resource:\/\/(?:\/|gre|devtools)/,
-  /^chrome:\/\/(?:global|browser|devtools)/,
-]);
-
-// Mapping of regexes to sample rates; if the regex matches the module an error
-// is thrown from, the matching sample rate is used instead of the default.
-// In case of a conflict, the first matching rate by insertion order is used.
-const MODULE_SAMPLE_RATES = new Map([
-  [/^(?:chrome|resource):\/\/devtools/, 1],
-  [/^moz-extension:\/\//, 0],
-]);
-
-/**
- * Collects nsIScriptError messages logged to the browser console and reports
- * them to a remotely-hosted error collection service.
- *
- * This is a PROTOTYPE; it will be removed in the future and potentially
- * replaced with a more robust implementation. It is meant to only collect
- * errors from Nightly (and local builds if enabled for development purposes)
- * and has not been reviewed for use outside of Nightly.
- *
- * The outgoing requests are designed to be compatible with Sentry. See
- * https://docs.sentry.io/clientdev/ for details on the data format that Sentry
- * expects.
- *
- * Errors may contain PII, such as in messages or local file paths in stack
- * traces; see bug 1426482 for privacy review and server-side mitigation.
- */
-class BrowserErrorReporter {
-  /**
-   * Generate a Date object corresponding to the date in the appBuildId.
-   */
-  static getAppBuildIdDate() {
-    const appBuildId = Services.appinfo.appBuildID;
-    const buildYear = Number.parseInt(appBuildId.slice(0, 4));
-    // Date constructor uses 0-indexed months
-    const buildMonth = Number.parseInt(appBuildId.slice(4, 6)) - 1;
-    const buildDay = Number.parseInt(appBuildId.slice(6, 8));
-    return new Date(buildYear, buildMonth, buildDay);
-  }
-
-  constructor(options = {}) {
-    // Test arguments for mocks and changing behavior
-    const defaultOptions = {
-      fetch: defaultFetch,
-      now: null,
-      chromeOnly: true,
-      sampleRates: MODULE_SAMPLE_RATES,
-      registerListener: () => Services.console.registerListener(this),
-      unregisterListener: () => Services.console.unregisterListener(this),
-    };
-    for (const [key, defaultValue] of Object.entries(defaultOptions)) {
-      this[key] = key in options ? options[key] : defaultValue;
-    }
-
-    XPCOMUtils.defineLazyGetter(this, "appBuildIdDate", BrowserErrorReporter.getAppBuildIdDate);
-
-    // Values that don't change between error reports.
-    this.requestBodyTemplate = {
-      logger: "javascript",
-      platform: "javascript",
-      release: Services.appinfo.appBuildID,
-      environment: UpdateUtils.getUpdateChannel(false),
-      contexts: {
-        os: {
-          name: PLATFORM_NAMES[AppConstants.platform],
-          version: (
-            Cc["@mozilla.org/network/protocol;1?name=http"]
-            .getService(Ci.nsIHttpProtocolHandler)
-            .oscpu
-          ),
-        },
-        browser: {
-          name: "Firefox",
-          version: Services.appinfo.version,
-        },
-      },
-      tags: {
-        changeset: AppConstants.SOURCE_REVISION_URL,
-      },
-      sdk: {
-        name: SDK_NAME,
-        version: SDK_VERSION,
-      },
-    };
-
-    XPCOMUtils.defineLazyPreferenceGetter(
-      this,
-      "collectionEnabled",
-      PREF_ENABLED,
-      false,
-      this.handleEnabledPrefChanged.bind(this),
-    );
-    XPCOMUtils.defineLazyPreferenceGetter(
-      this,
-      "sampleRatePref",
-      PREF_SAMPLE_RATE,
-      "0.0",
-      this.handleSampleRatePrefChanged.bind(this),
-    );
-
-    // Prefix mappings for the mangleFilePaths transform.
-    this.manglePrefixes = options.manglePrefixes || {
-      greDir: Services.dirsvc.get("GreD", Ci.nsIFile),
-      profileDir: Services.dirsvc.get("ProfD", Ci.nsIFile),
-    };
-    // File paths are encoded by nsIURI, so let's do the same for the prefixes
-    // we're comparing them to.
-    for (const [name, prefixFile] of Object.entries(this.manglePrefixes)) {
-      let filePath = Services.io.newFileURI(prefixFile).filePath;
-
-      // filePath might not have a trailing slash in some cases
-      if (!filePath.endsWith("/")) {
-        filePath += "/";
-      }
-
-      this.manglePrefixes[name] = filePath;
-    }
-  }
-
-  /**
-   * Lazily-created logger
-   */
-  get logger() {
-    const logger = Log.repository.getLogger("BrowserErrorReporter");
-    logger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
-    logger.manageLevelFromPref(PREF_LOG_LEVEL);
-
-    Object.defineProperty(this, "logger", {value: logger});
-    return this.logger;
-  }
-
-  init() {
-    if (this.collectionEnabled) {
-      this.registerListener();
-
-      // Processing already-logged messages in case any errors occurred before
-      // startup.
-      for (const message of Services.console.getMessageArray()) {
-        this.observe(message);
-      }
-    }
-  }
-
-  uninit() {
-    try {
-      this.unregisterListener();
-    } catch (err) {} // It probably wasn't registered.
-  }
-
-  handleEnabledPrefChanged(prefName, previousValue, newValue) {
-    if (newValue) {
-      this.registerListener();
-    } else {
-      try {
-        this.unregisterListener();
-      } catch (err) {} // It probably wasn't registered.
-    }
-  }
-
-  handleSampleRatePrefChanged(prefName, previousValue, newValue) {
-    Services.telemetry.scalarSet(TELEMETRY_ERROR_SAMPLE_RATE, newValue);
-  }
-
-  errorCollectedFilenameKey(filename) {
-    for (const pattern of TELEMETRY_REPORTED_PATTERNS) {
-      if (filename.match(pattern)) {
-        return filename;
-      }
-    }
-
-    // WebExtensions get grouped separately from other errors
-    if (filename.startsWith("moz-extension://")) {
-        return "MOZEXTENSION";
-    }
-
-    return "FILTERED";
-  }
-
-  isRecentBuild() {
-    // The local clock is not reliable, but this method doesn't need to be
-    // perfect.
-    const now = this.now || new Date();
-    return (now - this.appBuildIdDate) <= RECENT_BUILD_AGE;
-  }
-
-  observe(message) {
-    if (message instanceof Ci.nsIScriptError) {
-      ChromeUtils.idleDispatch(() => this.handleMessage(message));
-    }
-  }
-
-  async handleMessage(message) {
-    const isWarning = message.flags & message.warningFlag;
-    const isFromChrome = REPORTED_CATEGORIES.has(message.category);
-    if ((this.chromeOnly && !isFromChrome) || isWarning) {
-      return;
-    }
-
-    // Record that we collected an error prior to applying the sample rate
-    Services.telemetry.scalarAdd(TELEMETRY_ERROR_COLLECTED, 1);
-    if (message.stack) {
-      Services.telemetry.scalarAdd(TELEMETRY_ERROR_COLLECTED_STACK, 1);
-    }
-    if (message.sourceName) {
-      const key = this.errorCollectedFilenameKey(message.sourceName);
-      Services.telemetry.keyedScalarAdd(TELEMETRY_ERROR_COLLECTED_FILENAME, key.slice(0, 69), 1);
-    }
-
-    // We do not collect errors on non-Nightly channels, just telemetry.
-    // Also, old builds should not send errors to Sentry
-    if (!AppConstants.NIGHTLY_BUILD || !this.isRecentBuild()) {
-      return;
-    }
-
-    // Sample the amount of errors we send out
-    let sampleRate = Number.parseFloat(this.sampleRatePref);
-    for (const [regex, rate] of this.sampleRates) {
-      if (message.sourceName.match(regex)) {
-        sampleRate = rate;
-        break;
-      }
-    }
-    if (!Number.isFinite(sampleRate) || (Math.random() >= sampleRate)) {
-      return;
-    }
-
-    const exceptionValue = {};
-    const requestBody = {
-      ...this.requestBodyTemplate,
-      timestamp: new Date().toISOString().slice(0, -1), // Remove trailing "Z"
-      project: Services.prefs.getCharPref(PREF_PROJECT_ID),
-      exception: {
-        values: [exceptionValue],
-      },
-    };
-
-    const transforms = [
-      addErrorMessage,
-      addStacktrace,
-      addModule,
-      mangleExtensionUrls,
-      this.mangleFilePaths.bind(this),
-      tagExtensionErrors,
-    ];
-    for (const transform of transforms) {
-      await transform(message, exceptionValue, requestBody);
-    }
-
-    const url = new URL(Services.prefs.getCharPref(PREF_SUBMIT_URL));
-    url.searchParams.set("sentry_client", `${SDK_NAME}/${SDK_VERSION}`);
-    url.searchParams.set("sentry_version", "7");
-    url.searchParams.set("sentry_key", Services.prefs.getCharPref(PREF_PUBLIC_KEY));
-
-    try {
-      await this.fetch(url, {
-        method: "POST",
-        headers: {
-          "Content-Type": "application/json",
-          "Accept": "application/json",
-        },
-        // Sentry throws an auth error without a referrer specified.
-        referrer: "https://fake.mozilla.org",
-        body: JSON.stringify(requestBody),
-      });
-      Services.telemetry.scalarAdd(TELEMETRY_ERROR_REPORTED, 1);
-      this.logger.debug(`Sent error "${message.errorMessage}" successfully.`);
-    } catch (error) {
-      Services.telemetry.scalarAdd(TELEMETRY_ERROR_REPORTED_FAIL, 1);
-      this.logger.warn(`Failed to send error "${message.errorMessage}": ${error}`);
-    }
-  }
-
-  /**
-   * Alters file: and jar: paths to remove leading file paths that may contain
-   * user-identifying or platform-specific paths.
-   *
-   * prefixes is a mapping of replacementName -> filePath, where filePath is a
-   * path on the filesystem that should be replaced, and replacementName is the
-   * text that will replace it.
-   */
-  mangleFilePaths(message, exceptionValue) {
-    exceptionValue.module = this._transformFilePath(exceptionValue.module);
-    for (const frame of exceptionValue.stacktrace.frames) {
-      frame.module = this._transformFilePath(frame.module);
-    }
-  }
-
-  _transformFilePath(path) {
-    try {
-      const uri = Services.io.newURI(path);
-      if (uri.schemeIs("jar")) {
-        return uri.filePath;
-      }
-      if (uri.schemeIs("file")) {
-        for (const [name, prefix] of Object.entries(this.manglePrefixes)) {
-          if (uri.filePath.startsWith(prefix)) {
-            return uri.filePath.replace(prefix, `[${name}]/`);
-          }
-        }
-
-        return "[UNKNOWN_LOCAL_FILEPATH]";
-      }
-    } catch (err) {}
-
-    return path;
-  }
-}
-
-function defaultFetch(...args) {
-  // Do not make network requests while running in automation
-  if (Cu.isInAutomation) {
-    return null;
-  }
-
-  return fetch(...args);
-}
-
-function addErrorMessage(message, exceptionValue) {
-  // Parse the error type from the message if present (e.g. "TypeError: Whoops").
-  let errorMessage = message.errorMessage;
-  let errorName = "Error";
-  if (message.errorMessage.match(ERROR_PREFIX_RE)) {
-    const parts = message.errorMessage.split(":");
-    errorName = parts[0];
-    errorMessage = parts.slice(1).join(":").trim();
-  }
-
-  exceptionValue.type = errorName;
-  exceptionValue.value = errorMessage;
-}
-
-async function addStacktrace(message, exceptionValue) {
-  const frames = [];
-  let frame = message.stack;
-  // Avoid an infinite loop by limiting traces to 100 frames.
-  while (frame && frames.length < 100) {
-    const normalizedFrame = {
-      function: frame.functionDisplayName,
-      module: frame.source,
-      lineno: frame.line,
-      colno: frame.column,
-    };
-
-    try {
-      const response = await fetch(frame.source);
-      const sourceCode = await response.text();
-      const sourceLines = sourceCode.split(/\r?\n/);
-      // HTML pages and some inline event handlers have 0 as their line number
-      let lineIndex = Math.max(frame.line - 1, 0);
-
-      // XBL line numbers are off by one, and pretty much every XML file with JS
-      // in it is an XBL file.
-      if (frame.source.endsWith(".xml") && lineIndex > 0) {
-        lineIndex--;
-      }
-
-      normalizedFrame.context_line = sourceLines[lineIndex];
-      normalizedFrame.pre_context = sourceLines.slice(
-        Math.max(lineIndex - CONTEXT_LINES, 0),
-        lineIndex,
-      );
-      normalizedFrame.post_context = sourceLines.slice(
-        lineIndex + 1,
-        Math.min(lineIndex + 1 + CONTEXT_LINES, sourceLines.length),
-      );
-    } catch (err) {
-      // Could be a fetch issue, could be a line index issue. Not much we can
-      // do to recover in either case.
-    }
-
-    frames.push(normalizedFrame);
-    frame = frame.parent;
-  }
-  // Frames are sent in order from oldest to newest.
-  frames.reverse();
-
-  exceptionValue.stacktrace = {frames};
-}
-
-function addModule(message, exceptionValue) {
-  exceptionValue.module = message.sourceName;
-}
-
-function mangleExtensionUrls(message, exceptionValue) {
-  const extensions = new Map();
-  for (let extension of WebExtensionPolicy.getActiveExtensions()) {
-    extensions.set(extension.mozExtensionHostname, extension);
-  }
-
-  // Replaces any instances of moz-extension:// URLs with internal UUIDs to use
-  // the add-on ID instead.
-  function mangleExtURL(string, anchored = true) {
-    if (!string) {
-      return string;
-    }
-
-    const re = new RegExp(`${anchored ? "^" : ""}moz-extension://([^/]+)/`, "g");
-    return string.replace(re, (m0, m1) => {
-      const id = extensions.has(m1) ? extensions.get(m1).id : m1;
-      return `moz-extension://${id}/`;
-    });
-  }
-
-  exceptionValue.value = mangleExtURL(exceptionValue.value, false);
-  exceptionValue.module = mangleExtURL(exceptionValue.module);
-  for (const frame of exceptionValue.stacktrace.frames) {
-    frame.module = mangleExtURL(frame.module);
-  }
-}
-
-function tagExtensionErrors(message, exceptionValue, requestBody) {
-  requestBody.tags.isExtensionError = !!(
-      exceptionValue.module && exceptionValue.module.startsWith("moz-extension://")
-  );
-}
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -120,17 +120,16 @@ BROWSER_CHROME_MANIFESTS += [
     'test/browser/browser.ini',
     'test/browser/formValidation/browser.ini',
 ]
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 EXTRA_JS_MODULES += [
     'AboutNewTab.jsm',
     'AsyncTabSwitcher.jsm',
-    'BrowserErrorReporter.jsm',
     'BrowserUsageTelemetry.jsm',
     'BrowserWindowTracker.jsm',
     'ContentClick.jsm',
     'ContentCrashHandlers.jsm',
     'ContentMetaHandler.jsm',
     'ContentObservers.js',
     'ContentSearch.jsm',
     'Discovery.jsm',
--- a/browser/modules/test/browser/browser.ini
+++ b/browser/modules/test/browser/browser.ini
@@ -1,23 +1,14 @@
 [DEFAULT]
 support-files =
   head.js
 prefs =
   browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar=false
 
-[browser_BrowserErrorReporter.js]
-skip-if = (verify && !debug && (os == 'mac' || os == 'win'))
-support-files =
-  head_BrowserErrorReporter.js
-[browser_BrowserErrorReporter_nightly.js]
-skip-if = !nightly_build || (verify && !debug && (os == 'mac' || os == 'win'))
-support-files =
-  head_BrowserErrorReporter.js
-  browser_BrowserErrorReporter.html
 [browser_BrowserWindowTracker.js]
 [browser_ContentSearch.js]
 support-files =
   contentSearch.js
   contentSearchBadImage.xml
   contentSearchSuggestions.sjs
   contentSearchSuggestions.xml
   !/browser/components/search/test/browser/head.js
deleted file mode 100644
--- a/browser/modules/test/browser/browser_BrowserErrorReporter.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Test page</title>
-  </head>
-  <body>
-    <script>
-      // Line and column numbers are significant and used in tests, make sure to
-      // update the tests if you make any changes to this file!
-      function madeToFail() {
-        madeToFail2();
-      }
-      function madeToFail2() {
-        throw new Error("testFetchArguments error");
-      }
-      madeToFail();
-    </script>
-  </body>
-</html>
deleted file mode 100644
--- a/browser/modules/test/browser/browser_BrowserErrorReporter.js
+++ /dev/null
@@ -1,202 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
-
-// This file contains BrowserErrorReporter tests that don't depend on
-// errors being collected, which is only enabled on Nightly builds.
-
-ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
-ChromeUtils.import("resource:///modules/BrowserErrorReporter.jsm", this);
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this);
-ChromeUtils.import("resource://gre/modules/FileUtils.jsm", this);
-ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
-
-/* global sinon */
-Services.scriptloader.loadSubScript(new URL("head_BrowserErrorReporter.js", gTestPath).href, this);
-
-add_task(async function testInitPrefDisabled() {
-  let listening = false;
-  const reporter = new BrowserErrorReporter({
-    registerListener() {
-      listening = true;
-    },
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, false],
-  ]});
-
-  reporter.init();
-  ok(!listening, "Reporter does not listen for errors if the enabled pref is false.");
-});
-
-add_task(async function testInitUninitPrefEnabled() {
-  let listening = false;
-  const reporter = new BrowserErrorReporter({
-    registerListener() {
-      listening = true;
-    },
-    unregisterListener() {
-      listening = false;
-    },
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-  ]});
-
-  reporter.init();
-  ok(listening, "Reporter listens for errors if the enabled pref is true.");
-
-  reporter.uninit();
-  ok(!listening, "Reporter does not listen for errors after uninit.");
-});
-
-add_task(async function testEnabledPrefWatcher() {
-  let listening = false;
-  const reporter = new BrowserErrorReporter({
-    registerListener() {
-      listening = true;
-    },
-    unregisterListener() {
-      listening = false;
-    },
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, false],
-  ]});
-
-  reporter.init();
-  ok(!listening, "Reporter does not collect errors if the enable pref is false.");
-
-  Services.console.logMessage(createScriptError({message: "Shouldn't report"}));
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-  ]});
-  ok(listening, "Reporter collects errors if the enabled pref switches to true.");
-});
-
-add_task(async function testScalars() {
-  // Do not bother testing telemetry scalars if they're already expired.
-  if (SCALARS_EXPIRED) {
-    return;
-  }
-
-  const fetchStub = sinon.stub();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchStub,
-    sampleRates: new Map(),
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  Services.telemetry.clearScalars();
-
-  const messages = [
-    createScriptError({message: "No name"}),
-    createScriptError({message: "Also no name", sourceName: "resource://gre/modules/Foo.jsm"}),
-    createScriptError({message: "More no name", sourceName: "resource://gre/modules/Bar.jsm"}),
-    createScriptError({message: "Yeah sures", sourceName: "unsafe://gre/modules/Bar.jsm"}),
-    createScriptError({message: "Addon", sourceName: "moz-extension://foo/Bar.jsm"}),
-    createScriptError({
-      message: "long",
-      sourceName: "resource://gre/modules/long/long/long/long/long/long/long/long/long/long/",
-    }),
-    {message: "Not a scripterror instance."},
-    createScriptError({message: "Whatever", stack: [frame()]}),
-  ];
-
-  // Use handleMessage to avoid errors from other code messing up our counts.
-  for (const message of messages) {
-    await reporter.handleMessage(message);
-  }
-
-  const scalars = Services.telemetry.getSnapshotForScalars("main", false).parent;
-  is(
-    scalars[TELEMETRY_ERROR_COLLECTED],
-    7,
-    `${TELEMETRY_ERROR_COLLECTED} is incremented when an error is collected.`,
-  );
-  is(
-    scalars[TELEMETRY_ERROR_COLLECTED_STACK],
-    1,
-    `${TELEMETRY_ERROR_REPORTED_FAIL} is incremented when an error with a stack trace is collected.`,
-  );
-
-  const keyedScalars = Services.telemetry.getSnapshotForKeyedScalars("main", false).parent;
-  Assert.deepEqual(
-    keyedScalars[TELEMETRY_ERROR_COLLECTED_FILENAME],
-    {
-      "FILTERED": 1,
-      "MOZEXTENSION": 1,
-      "resource://gre/modules/Foo.jsm": 1,
-      "resource://gre/modules/Bar.jsm": 1,
-      // Cut off at 70-character limit
-      "resource://gre/modules/long/long/long/long/long/long/long/long/long/l": 1,
-    },
-    `${TELEMETRY_ERROR_COLLECTED_FILENAME} is incremented when an error is collected.`,
-  );
-
-  resetConsole();
-});
-
-add_task(async function testCollectedFilenameScalar() {
-  // Do not bother testing telemetry scalars if they're already expired.
-  if (SCALARS_EXPIRED) {
-    return;
-  }
-
-  const fetchStub = sinon.stub();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchStub,
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  const testCases = [
-    ["chrome://unknown/module.jsm", false],
-    ["resource://unknown/module.jsm", false],
-    ["unknown://unknown/module.jsm", false],
-
-    ["resource://gre/modules/Foo.jsm", true],
-    ["resource:///modules/Foo.jsm", true],
-    ["chrome://global/Foo.jsm", true],
-    ["chrome://browser/Foo.jsm", true],
-    ["chrome://devtools/Foo.jsm", true],
-  ];
-
-  for (const [filename, shouldMatch] of testCases) {
-    Services.telemetry.clearScalars();
-
-    // Use handleMessage to avoid errors from other code messing up our counts.
-    await reporter.handleMessage(createScriptError({
-      message: "Fine",
-      sourceName: filename,
-    }));
-
-    const keyedScalars = (
-      Services.telemetry.getSnapshotForKeyedScalars("main", false).parent
-    );
-
-    let matched = null;
-    if (shouldMatch) {
-      matched = keyedScalars[TELEMETRY_ERROR_COLLECTED_FILENAME][filename] === 1;
-    } else {
-      matched = keyedScalars[TELEMETRY_ERROR_COLLECTED_FILENAME].FILTERED === 1;
-    }
-
-    ok(
-      matched,
-      shouldMatch
-        ? `${TELEMETRY_ERROR_COLLECTED_FILENAME} logs a key for ${filename}.`
-        : `${TELEMETRY_ERROR_COLLECTED_FILENAME} logs a FILTERED key for ${filename}.`,
-    );
-  }
-
-  resetConsole();
-});
deleted file mode 100644
--- a/browser/modules/test/browser/browser_BrowserErrorReporter_nightly.js
+++ /dev/null
@@ -1,548 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
-
-// This file contains BrowserErrorReporter tests that depend on errors
-// being collected, which is only enabled on Nightly builds.
-
-ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
-ChromeUtils.import("resource:///modules/BrowserErrorReporter.jsm", this);
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this);
-ChromeUtils.import("resource://gre/modules/FileUtils.jsm", this);
-ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
-
-/* global sinon */
-Services.scriptloader.loadSubScript(new URL("head_BrowserErrorReporter.js", gTestPath).href, this);
-
-add_task(async function testInitPastMessages() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchSpy,
-    registerListener: noop,
-    unregisterListener: noop,
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  resetConsole();
-  Services.console.logMessage(createScriptError({message: "Logged before init"}));
-  reporter.init();
-
-  // Include ok() to satisfy mochitest warning for test without any assertions
-  const errorWasLogged = await TestUtils.waitForCondition(
-    () => fetchPassedError(fetchSpy, "Logged before init"),
-    "Waiting for message to be logged",
-  );
-  ok(errorWasLogged, "Reporter collects errors logged before initialization.");
-
-  reporter.uninit();
-});
-
-add_task(async function testNonErrorLogs() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchSpy,
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  await reporter.handleMessage({message: "Not a scripterror instance."});
-  ok(
-    !fetchPassedError(fetchSpy, "Not a scripterror instance."),
-    "Reporter does not collect normal log messages or warnings.",
-  );
-
-  await reporter.handleMessage(createScriptError({
-    message: "Warning message",
-    flags: Ci.nsIScriptError.warningFlag,
-  }));
-  ok(
-    !fetchPassedError(fetchSpy, "Warning message"),
-    "Reporter does not collect normal log messages or warnings.",
-  );
-
-  await reporter.handleMessage(createScriptError({
-    message: "Non-chrome category",
-    category: "totally from a website",
-  }));
-  ok(
-    !fetchPassedError(fetchSpy, "Non-chrome category"),
-    "Reporter does not collect normal log messages or warnings.",
-  );
-
-  await reporter.handleMessage(createScriptError({message: "Is error"}));
-  ok(
-    fetchPassedError(fetchSpy, "Is error"),
-    "Reporter collects error messages.",
-  );
-});
-
-add_task(async function testSampling() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchSpy,
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  await reporter.handleMessage(createScriptError({message: "Should log"}));
-  ok(
-    fetchPassedError(fetchSpy, "Should log"),
-    "A 1.0 sample rate will cause the reporter to always collect errors.",
-  );
-
-  await reporter.handleMessage(createScriptError({message: "undefined", sourceName: undefined}));
-  ok(
-    fetchPassedError(fetchSpy, "undefined"),
-    "A missing sourceName doesn't break reporting.",
-  );
-
-  await reporter.handleMessage(createScriptError({
-    message: "mozextension",
-    sourceName: "moz-extension://Bar/Foo.jsm",
-  }));
-  ok(
-    !fetchPassedError(fetchSpy, "mozextension"),
-    "moz-extension:// paths are sampled at 0% even if the default rate is 1.0.",
-  );
-
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_SAMPLE_RATE, "0.0"],
-  ]});
-  await reporter.handleMessage(createScriptError({message: "Shouldn't log"}));
-  ok(
-    !fetchPassedError(fetchSpy, "Shouldn't log"),
-    "A 0.0 sample rate will cause the reporter to never collect errors.",
-  );
-
-  await reporter.handleMessage(createScriptError({
-    message: "chromedevtools",
-    sourceName: "chrome://devtools/Foo.jsm",
-  }));
-  ok(
-    fetchPassedError(fetchSpy, "chromedevtools"),
-    "chrome://devtools/ paths are sampled at 100% even if the default rate is 0.0.",
-  );
-
-  await reporter.handleMessage(createScriptError({
-    message: "resourcedevtools",
-    sourceName: "resource://devtools/Foo.jsm",
-  }));
-  ok(
-    fetchPassedError(fetchSpy, "resourcedevtools"),
-    "resource://devtools/ paths are sampled at 100% even if the default rate is 0.0.",
-  );
-
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_SAMPLE_RATE, ")fasdf"],
-  ]});
-  await reporter.handleMessage(createScriptError({message: "Also shouldn't log"}));
-  ok(
-    !fetchPassedError(fetchSpy, "Also shouldn't log"),
-    "An invalid sample rate will cause the reporter to never collect errors.",
-  );
-});
-
-add_task(async function testNameMessage() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchSpy,
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  await reporter.handleMessage(createScriptError({message: "No name"}));
-  let call = fetchCallForMessage(fetchSpy, "No name");
-  let body = JSON.parse(call.args[1].body);
-  is(
-    body.exception.values[0].type,
-    "Error",
-    "Reporter uses a generic type when no name is in the message.",
-  );
-  is(
-    body.exception.values[0].value,
-    "No name",
-    "Reporter uses error message as the exception value.",
-  );
-
-  await reporter.handleMessage(createScriptError({message: "FooError: Has name"}));
-  call = fetchCallForMessage(fetchSpy, "Has name");
-  body = JSON.parse(call.args[1].body);
-  is(
-    body.exception.values[0].type,
-    "FooError",
-    "Reporter uses the error type from the message.",
-  );
-  is(
-    body.exception.values[0].value,
-    "Has name",
-    "Reporter uses error message as the value parameter.",
-  );
-
-  await reporter.handleMessage(createScriptError({message: "FooError: Has :extra: colons"}));
-  call = fetchCallForMessage(fetchSpy, "Has :extra: colons");
-  body = JSON.parse(call.args[1].body);
-  is(
-    body.exception.values[0].type,
-    "FooError",
-    "Reporter uses the error type from the message.",
-  );
-  is(
-    body.exception.values[0].value,
-    "Has :extra: colons",
-    "Reporter uses error message as the value parameter.",
-  );
-});
-
-add_task(async function testRecentBuild() {
-  // Create date that is guaranteed to be a month newer than the build date.
-  const nowDate = BrowserErrorReporter.getAppBuildIdDate();
-  nowDate.setMonth(nowDate.getMonth() + 1);
-
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchSpy,
-    now: nowDate,
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  await reporter.handleMessage(createScriptError({message: "Is error"}));
-  ok(
-    !fetchPassedError(fetchSpy, "Is error"),
-    "Reporter does not collect errors from builds older than a week.",
-  );
-});
-
-add_task(async function testFetchArguments() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchSpy,
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-    [PREF_PROJECT_ID, "123"],
-    [PREF_PUBLIC_KEY, "foobar"],
-    [PREF_SUBMIT_URL, "https://errors.example.com/api/123/store/"],
-  ]});
-
-  resetConsole();
-  reporter.init();
-  const testPageUrl = (
-    "chrome://mochitests/content/browser/browser/modules/test/browser/" +
-    "browser_BrowserErrorReporter.html"
-  );
-
-  SimpleTest.expectUncaughtException();
-  await BrowserTestUtils.withNewTab(testPageUrl, async () => {
-    const call = await TestUtils.waitForCondition(
-      () => fetchCallForMessage(fetchSpy, "testFetchArguments error"),
-      "Wait for error from browser_BrowserErrorReporter.html to be logged",
-    );
-    const body = JSON.parse(call.args[1].body);
-    const url = new URL(call.args[0]);
-
-    is(url.origin, "https://errors.example.com", "Reporter builds API url from DSN pref.");
-    is(url.pathname, "/api/123/store/", "Reporter builds API url from DSN pref.");
-    is(
-      url.searchParams.get("sentry_client"),
-      "firefox-error-reporter/1.0.0",
-      "Reporter identifies itself in the outgoing request",
-    );
-    is(url.searchParams.get("sentry_version"), "7", "Reporter is compatible with Sentry 7.");
-    is(url.searchParams.get("sentry_key"), "foobar", "Reporter pulls API key from DSN pref.");
-    is(body.project, "123", "Reporter pulls project ID from DSN pref.");
-    is(
-      body.tags.changeset,
-      AppConstants.SOURCE_REVISION_URL,
-      "Reporter pulls changeset tag from AppConstants",
-    );
-    is(call.args[1].referrer, "https://fake.mozilla.org", "Reporter uses a fake referer.");
-
-    const response = await fetch(testPageUrl);
-    const pageText = await response.text();
-    const pageLines = pageText.split("\n");
-    Assert.deepEqual(
-      body.exception,
-      {
-        values: [
-          {
-            type: "Error",
-            value: "testFetchArguments error",
-            module: testPageUrl,
-            stacktrace: {
-              frames: [
-                {
-                  function: null,
-                  module: testPageUrl,
-                  lineno: 17,
-                  colno: 7,
-                  pre_context: pageLines.slice(11, 16),
-                  context_line: pageLines[16],
-                  post_context: pageLines.slice(17, 22),
-                },
-                {
-                  function: "madeToFail",
-                  module: testPageUrl,
-                  lineno: 12,
-                  colno: 9,
-                  pre_context: pageLines.slice(6, 11),
-                  context_line: pageLines[11],
-                  post_context: pageLines.slice(12, 17),
-                },
-                {
-                  function: "madeToFail2",
-                  module: testPageUrl,
-                  lineno: 15,
-                  colno: 15,
-                  pre_context: pageLines.slice(9, 14),
-                  context_line: pageLines[14],
-                  post_context: pageLines.slice(15, 20),
-                },
-              ],
-            },
-          },
-        ],
-      },
-      "Reporter builds stack trace from scriptError correctly.",
-    );
-  });
-
-  reporter.uninit();
-});
-
-add_task(async function testAddonIDMangle() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchSpy,
-    chromeOnly: false,
-    sampleRates: new Map(),
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-  resetConsole();
-  reporter.init();
-
-  // Create and install test add-on
-  const id = "browsererrorcollection@example.com";
-  const extension = ExtensionTestUtils.loadExtension({
-    manifest: {
-      applications: {
-        gecko: { id },
-      },
-    },
-    background() {
-      throw new Error("testAddonIDMangle error");
-    },
-  });
-  await extension.startup();
-
-  // Just in case the error hasn't been thrown before add-on startup.
-  const call = await TestUtils.waitForCondition(
-    () => fetchCallForMessage(fetchSpy, "testAddonIDMangle error"),
-    `Wait for error from ${id} to be logged`,
-  );
-  const body = JSON.parse(call.args[1].body);
-  const stackFrame = body.exception.values[0].stacktrace.frames[0];
-  ok(
-    stackFrame.module.startsWith(`moz-extension://${id}/`),
-    "Stack frame filenames use the proper add-on ID instead of internal UUIDs.",
-  );
-
-  await extension.unload();
-  reporter.uninit();
-});
-
-add_task(async function testExtensionTag() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchSpy,
-    chromeOnly: false,
-    sampleRates: new Map(),
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-  resetConsole();
-  reporter.init();
-
-  // Create and install test add-on
-  const id = "browsererrorcollection@example.com";
-  const extension = ExtensionTestUtils.loadExtension({
-    manifest: {
-      applications: {
-        gecko: { id },
-      },
-    },
-    background() {
-      throw new Error("testExtensionTag error");
-    },
-  });
-  await extension.startup();
-
-  // Just in case the error hasn't been thrown before add-on startup.
-  let call = await TestUtils.waitForCondition(
-    () => fetchCallForMessage(fetchSpy, "testExtensionTag error"),
-    `Wait for error from ${id} to be logged`,
-  );
-  let body = JSON.parse(call.args[1].body);
-  ok(body.tags.isExtensionError, "Errors from extensions have an isExtensionError=true tag.");
-
-  await extension.unload();
-  reporter.uninit();
-
-  await reporter.handleMessage(createScriptError({message: "testExtensionTag not from extension"}));
-  call = fetchCallForMessage(fetchSpy, "testExtensionTag not from extension");
-  body = JSON.parse(call.args[1].body);
-  is(body.tags.isExtensionError, false, "Normal errors have an isExtensionError=false tag.");
-});
-
-add_task(async function testScalars() {
-  // Do not bother testing telemetry scalars if they're already expired.
-  if (SCALARS_EXPIRED) {
-    return;
-  }
-
-  const fetchStub = sinon.stub();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchStub,
-    now: BrowserErrorReporter.getAppBuildIdDate(),
-  });
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  Services.telemetry.clearScalars();
-
-  // Basic count
-  await reporter.handleMessage(createScriptError({message: "No name"}));
-
-  // Sample rate affects counts
-  await SpecialPowers.pushPrefEnv({set: [[PREF_SAMPLE_RATE, "0.0"]]});
-  await reporter.handleMessage(createScriptError({message: "Additionally no name"}));
-
-  // Failed fetches should be counted too
-  await SpecialPowers.pushPrefEnv({set: [[PREF_SAMPLE_RATE, "1.0"]]});
-  fetchStub.rejects(new Error("Could not report"));
-  await reporter.handleMessage(createScriptError({message: "Maybe name?"}));
-
-  const scalars = Services.telemetry.getSnapshotForScalars("main", false).parent;
-  is(
-    scalars[TELEMETRY_ERROR_COLLECTED],
-    3,
-    `${TELEMETRY_ERROR_COLLECTED} is incremented when an error is collected.`,
-  );
-  is(
-    scalars[TELEMETRY_ERROR_SAMPLE_RATE],
-    "1.0",
-    `${TELEMETRY_ERROR_SAMPLE_RATE} contains the last sample rate used.`,
-  );
-  is(
-    scalars[TELEMETRY_ERROR_REPORTED],
-    1,
-    `${TELEMETRY_ERROR_REPORTED} is incremented when an error is reported.`,
-  );
-  is(
-    scalars[TELEMETRY_ERROR_REPORTED_FAIL],
-    1,
-    `${TELEMETRY_ERROR_REPORTED_FAIL} is incremented when an error fails to be reported.`,
-  );
-
-  resetConsole();
-});
-
-add_task(async function testFilePathMangle() {
-  const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({fetch: fetchSpy});
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  const greDir = Services.dirsvc.get("GreD", Ci.nsIFile).path;
-  const profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
-
-  const message = createScriptError({
-    message: "Whatever",
-    sourceName: "file:///path/to/main.jsm",
-    stack: [
-      frame({source: "jar:file:///path/to/jar!/inside/jar.jsm"}),
-      frame({source: `file://${greDir}/defaults/prefs/channel-prefs.js`}),
-      frame({source: `file://${profileDir}/prefs.js`}),
-    ],
-  });
-  await reporter.handleMessage(message);
-
-  const call = fetchCallForMessage(fetchSpy, "Whatever");
-  const body = JSON.parse(call.args[1].body);
-  const exception = body.exception.values[0];
-  is(exception.module, "[UNKNOWN_LOCAL_FILEPATH]", "Unrecognized local file paths are mangled");
-
-  // Stackframe order is reversed from what is in the message.
-  const stackFrames = exception.stacktrace.frames;
-  is(
-    stackFrames[0].module, "[profileDir]/prefs.js",
-    "Paths within the profile directory are preserved but mangled",
-  );
-  is(
-    stackFrames[1].module, "[greDir]/defaults/prefs/channel-prefs.js",
-    "Paths within the GRE directory are preserved but mangled",
-  );
-  is(
-    stackFrames[2].module, "/inside/jar.jsm",
-    "Paths within jarfiles are extracted from the full jar: URL",
-  );
-});
-
-add_task(async function testFilePathMangleWhitespace() {
-  const fetchSpy = sinon.spy();
-
-  const greDir = Services.dirsvc.get("GreD", Ci.nsIFile);
-  const whitespaceDir = greDir.clone();
-  whitespaceDir.append("with whitespace");
-  const manglePrefixes = {
-    whitespace: whitespaceDir,
-  };
-
-  const reporter = new BrowserErrorReporter({fetch: fetchSpy, manglePrefixes});
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  const message = createScriptError({
-    message: "Whatever",
-    sourceName: `file://${greDir.path}/with whitespace/remaining/file.jsm`,
-  });
-  await reporter.handleMessage(message);
-
-  const call = fetchCallForMessage(fetchSpy, "Whatever");
-  const body = JSON.parse(call.args[1].body);
-  const exception = body.exception.values[0];
-  is(
-    exception.module, "[whitespace]/remaining/file.jsm",
-    "Prefixes with whitespace are correctly mangled",
-  );
-});
deleted file mode 100644
--- a/browser/modules/test/browser/head_BrowserErrorReporter.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
-
-/* exported sinon */
-Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
-registerCleanupFunction(function() {
-  delete window.sinon;
-});
-
-const PREF_ENABLED = "browser.chrome.errorReporter.enabled";
-const PREF_PROJECT_ID = "browser.chrome.errorReporter.projectId";
-const PREF_PUBLIC_KEY = "browser.chrome.errorReporter.publicKey";
-const PREF_SAMPLE_RATE = "browser.chrome.errorReporter.sampleRate";
-const PREF_SUBMIT_URL = "browser.chrome.errorReporter.submitUrl";
-const TELEMETRY_ERROR_COLLECTED = "browser.errors.collected_count";
-const TELEMETRY_ERROR_COLLECTED_FILENAME = "browser.errors.collected_count_by_filename";
-const TELEMETRY_ERROR_COLLECTED_STACK = "browser.errors.collected_with_stack_count";
-const TELEMETRY_ERROR_REPORTED = "browser.errors.reported_success_count";
-const TELEMETRY_ERROR_REPORTED_FAIL = "browser.errors.reported_failure_count";
-const TELEMETRY_ERROR_SAMPLE_RATE = "browser.errors.sample_rate";
-
-const currentVersion = Services.appinfo.platformVersion;
-const expiringVersion = "64.0";
-const SCALARS_EXPIRED = Services.vc.compare(currentVersion, expiringVersion) === 1;
-
-add_task(async function testSetup() {
-  const canRecordExtended = Services.telemetry.canRecordExtended;
-  Services.telemetry.canRecordExtended = true;
-  registerCleanupFunction(() => Services.telemetry.canRecordExtended = canRecordExtended);
-});
-
-function createScriptError(options = {}) {
-  let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
-  scriptError.init(
-    options.message || "",
-    "sourceName" in options ? options.sourceName : null,
-    options.sourceLine || null,
-    options.lineNumber || null,
-    options.columnNumber || null,
-    options.flags || Ci.nsIScriptError.errorFlag,
-    options.category || "chrome javascript",
-  );
-
-  // You can't really set the stack of a scriptError in JS, so we shadow it instead.
-   if (options.stack) {
-     scriptError = Object.create(scriptError, {
-       stack: {
-         value: createStack(options.stack),
-       },
-     });
-   }
-
-  return scriptError;
-}
-
-function createStack(frames) {
-  for (let k = 0; k < frames.length - 1; k++) {
-    frames[k].parent = frames[k + 1];
-  }
-  return frames[0];
-}
-
-function frame(options = {}) {
-  return Object.assign({
-    functionDisplayName: "fooFunction",
-    source: "resource://modules/BrowserErrorReporter.jsm",
-    line: 5,
-    column: 10,
-  }, options);
-}
-
-function noop() {
-  // Do nothing
-}
-
-// Clears the console of any previous messages. Should be called at the end of
-// each test that logs to the console.
-function resetConsole() {
-  Services.console.logStringMessage("");
-  Services.console.reset();
-}
-
-// Finds the fetch spy call for an error with a matching message.
-function fetchCallForMessage(fetchSpy, message) {
-  for (const call of fetchSpy.getCalls()) {
-    const body = JSON.parse(call.args[1].body);
-    if (body.exception.values[0].value.includes(message)) {
-      return call;
-    }
-  }
-
-  return null;
-}
-
-// Helper to test if a fetch spy was called with the given error message.
-// Used in tests where unrelated JS errors from other code are logged.
-function fetchPassedError(fetchSpy, message) {
-  return fetchCallForMessage(fetchSpy, message) !== null;
-}
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -419,22 +419,17 @@ void BrowsingContext::Focus(ErrorResult&
 void BrowsingContext::Blur(ErrorResult& aError) {
   ContentChild* cc = ContentChild::GetSingleton();
   cc->SendWindowBlur(BrowsingContextId(mBrowsingContextId));
 }
 
 Nullable<WindowProxyHolder> BrowsingContext::GetTop(ErrorResult& aError) {
   // We never return null or throw an error, but the implementation in
   // nsGlobalWindow does and we need to use the same signature.
-  BrowsingContext* bc = this;
-  BrowsingContext* parent;
-  while ((parent = bc->mParent)) {
-    bc = parent;
-  }
-  return WindowProxyHolder(bc);
+  return WindowProxyHolder(TopLevelBrowsingContext());
 }
 
 void BrowsingContext::GetOpener(JSContext* aCx,
                                 JS::MutableHandle<JS::Value> aOpener,
                                 ErrorResult& aError) const {
   auto* opener = GetOpener();
   if (!opener) {
     aOpener.setNull();
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -115,18 +115,18 @@ partial interface HTMLMediaElement {
   [Pref="media.test.dumpDebugInfo"]
   Promise<void> mozDumpDebugInfo();
 
   attribute MediaStream? srcObject;
 
   attribute boolean mozPreservesPitch;
 
   // NB: for internal use with the video controls:
-  [Func="IsChromeOrXBL"] attribute boolean mozAllowCasting;
-  [Func="IsChromeOrXBL"] attribute boolean mozIsCasting;
+  [Func="IsChromeOrXBLOrUAWidget"] attribute boolean mozAllowCasting;
+  [Func="IsChromeOrXBLOrUAWidget"] attribute boolean mozIsCasting;
 
   // Mozilla extension: stream capture
   [Throws]
   MediaStream mozCaptureStream();
   [Throws]
   MediaStream mozCaptureStreamUntilEnded();
   readonly attribute boolean mozAudioCaptured;
 
new file mode 100644
--- /dev/null
+++ b/gfx/tests/crashtests/1505934-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<style>
+  body {
+      background-color: limegreen;
+  }
+  .A {
+      transform: scale(0.01) perspective(1000px);
+      transform-origin: 0% 0%;
+      filter: grayscale(40%);
+  }
+  .B {
+      background-color: black;
+      width: 10000px;
+      height: 5000px;
+      border-radius: 2000px;
+  }
+</style>
+<body>
+  <div class="A">
+      <div class = "B"/>
+  </div>
+</body>
--- a/gfx/tests/crashtests/crashtests.list
+++ b/gfx/tests/crashtests/crashtests.list
@@ -174,10 +174,11 @@ load 1490704-1.html
 load 1501518.html
 load 1503986-1.html
 load 1505426-1.html
 load 1508811.html
 load 1508822.html
 load 1509099.html
 load 1513133.html
 load 1496194.html
+load 1505934-1.html
 load 1509123.html
 load texture-allocator-zero-region.html
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -81,16 +81,27 @@ pub struct TileIndex(pub usize);
 
 /// The size in device pixels of a cached tile. The currently chosen
 /// size is arbitrary. We should do some profiling to find the best
 /// size for real world pages.
 pub const TILE_SIZE_WIDTH: i32 = 1024;
 pub const TILE_SIZE_HEIGHT: i32 = 256;
 const FRAMES_BEFORE_CACHING: usize = 2;
 
+/// The maximum size per axis of texture cache item,
+///  in WorldPixel coordinates.
+// TODO(gw): This size is quite arbitrary - we should do some
+//           profiling / telemetry to see when it makes sense
+//           to cache a picture.
+const MAX_CACHE_SIZE: f32 = 2048.0;
+/// The maximum size per axis of a surface,
+///  in WorldPixel coordinates.
+const MAX_SURFACE_SIZE: f32 = 4096.0;
+
+
 #[derive(Debug)]
 pub struct GlobalTransformInfo {
     /// Current (quantized) value of the transform, that is
     /// independent of the value of the spatial node index.
     /// Only calculated on first use.
     current: Option<TransformKey>,
     /// Tiles check this to see if the dependencies have changed.
     changed: bool,
@@ -1273,16 +1284,21 @@ impl SurfaceInfo {
             surface: None,
             raster_spatial_node_index,
             surface_spatial_node_index,
             tasks: Vec::new(),
             inflation_factor,
         }
     }
 
+    pub fn fits_surface_size_limits(&self) -> bool {
+        self.map_local_to_surface.bounds.size.width <= MAX_SURFACE_SIZE &&
+        self.map_local_to_surface.bounds.size.height <= MAX_SURFACE_SIZE
+    }
+
     /// Take the set of child render tasks for this surface. This is
     /// used when constructing the render task tree.
     pub fn take_render_tasks(&mut self) -> Vec<RenderTaskId> {
         mem::replace(&mut self.tasks, Vec::new())
     }
 }
 
 #[derive(Debug)]
@@ -1816,16 +1832,17 @@ impl PicturePrimitive {
                 Some(PlaneSplitter::new())
             }
             Picture3DContext::In { root_data: None, .. } => {
                 None
             }
         };
 
         let state = PictureState {
+            //TODO: check for MAX_CACHE_SIZE here?
             is_cacheable: true,
             map_local_to_pic,
             map_pic_to_world,
             map_pic_to_raster,
             map_raster_to_world,
             plane_splitter,
         };
 
@@ -2015,85 +2032,85 @@ impl PicturePrimitive {
             mode => mode,
         };
 
         if let Some(composite_mode) = actual_composite_mode {
             // Retrieve the positioning node information for the parent surface.
             let parent_raster_spatial_node_index = state.current_surface().raster_spatial_node_index;
             let surface_spatial_node_index = self.spatial_node_index;
 
-            // Check if there is perspective, and thus whether a new
-            // rasterization root should be established.
-            let xf = frame_context.clip_scroll_tree.get_relative_transform(
-                parent_raster_spatial_node_index,
-                surface_spatial_node_index,
-            ).expect("BUG: unable to get relative transform");
-
-            // TODO(gw): A temporary hack here to revert behavior to
-            //           always raster in screen-space. This is not
-            //           a problem yet, since we're not taking advantage
-            //           of this for caching yet. This is a workaround
-            //           for some existing issues with handling scale
-            //           when rasterizing in local space mode. Once
-            //           the fixes for those are in-place, we can
-            //           remove this hack!
-            //let local_scale = raster_space.local_scale();
-            // let wants_raster_root = xf.has_perspective_component() ||
-            //                         local_scale.is_some();
-            let establishes_raster_root = xf.has_perspective_component();
-
             // TODO(gw): For now, we always raster in screen space. Soon,
             //           we will be able to respect the requested raster
             //           space, and/or override the requested raster root
             //           if it makes sense to.
             let raster_space = RasterSpace::Screen;
 
-            let raster_spatial_node_index = if establishes_raster_root {
-                surface_spatial_node_index
-            } else {
-                parent_raster_spatial_node_index
-            };
-
             let inflation_factor = match composite_mode {
                 PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
                     // The amount of extra space needed for primitives inside
                     // this picture to ensure the visibility check is correct.
                     BLUR_SAMPLE_SCALE * blur_radius
                 }
                 _ => {
                     0.0
                 }
             };
 
-            let surface_index = state.push_surface(
+            let mut surface = {
+                // Check if there is perspective, and thus whether a new
+                // rasterization root should be established.
+                let xf = frame_context.clip_scroll_tree.get_relative_transform(
+                    parent_raster_spatial_node_index,
+                    surface_spatial_node_index,
+                ).expect("BUG: unable to get relative transform");
+
+                let establishes_raster_root = xf.has_perspective_component();
+
                 SurfaceInfo::new(
                     surface_spatial_node_index,
-                    raster_spatial_node_index,
+                    if establishes_raster_root {
+                        surface_spatial_node_index
+                    } else {
+                        parent_raster_spatial_node_index
+                    },
                     inflation_factor,
                     frame_context.screen_world_rect,
                     &frame_context.clip_scroll_tree,
                 )
-            );
+            };
 
-            self.raster_config = Some(RasterConfig {
-                composite_mode,
-                surface_index,
-                establishes_raster_root,
-            });
+            if surface_spatial_node_index != parent_raster_spatial_node_index &&
+                !surface.fits_surface_size_limits()
+            {
+                // fall back to the parent raster root
+                surface = SurfaceInfo::new(
+                    surface_spatial_node_index,
+                    parent_raster_spatial_node_index,
+                    inflation_factor,
+                    frame_context.screen_world_rect,
+                    &frame_context.clip_scroll_tree,
+                );
+            };
 
             // If we have a cache key / descriptor for this surface,
             // update any transforms it cares about.
             if let Some(ref mut surface_desc) = self.surface_desc {
                 surface_desc.update(
                     surface_spatial_node_index,
-                    raster_spatial_node_index,
+                    surface.raster_spatial_node_index,
                     frame_context.clip_scroll_tree,
                     raster_space,
                 );
             }
+
+            self.raster_config = Some(RasterConfig {
+                composite_mode,
+                establishes_raster_root: surface_spatial_node_index == surface.raster_spatial_node_index,
+                surface_index: state.push_surface(surface),
+            });
         }
 
         Some(mem::replace(&mut self.prim_list.pictures, SmallVec::new()))
     }
 
     /// Update the primitive dependencies for any active tile caches,
     /// but only *if* the transforms have made the mappings out of date.
     pub fn update_prim_dependencies(
@@ -2149,17 +2166,17 @@ impl PicturePrimitive {
                     Picture3DContext::In { root_data: None, ancestor_index } => {
                         ancestor_index
                     }
                 };
 
                 let map_local_to_containing_block: SpaceMapper<LayoutPixel, LayoutPixel> = SpaceMapper::new_with_target(
                     containing_block_index,
                     cluster.spatial_node_index,
-                    LayoutRect::zero(),     // bounds aren't going to be used for this mapping
+                    LayoutRect::zero(), // bounds aren't going to be used for this mapping
                     &frame_context.clip_scroll_tree,
                 );
 
                 match map_local_to_containing_block.visible_face() {
                     VisibleFace::Back => continue,
                     VisibleFace::Front => {}
                 }
             }
@@ -2336,20 +2353,16 @@ impl PicturePrimitive {
                 // dimensions, so that it can be reused as it scrolls into
                 // view etc. However, if the unclipped size of the surface is
                 // too big, then it will be very expensive to draw, and may
                 // even be bigger than the maximum hardware render target
                 // size. In these cases, it's probably best to not cache the
                 // picture, and just draw a minimal portion of the picture
                 // (clipped to screen bounds) to a temporary target each frame.
 
-                // TODO(gw): This size is quite arbitrary - we should do some
-                //           profiling / telemetry to see when it makes sense
-                //           to cache a picture.
-                const MAX_CACHE_SIZE: f32 = 2048.0;
                 let too_big_to_cache = unclipped.size.width > MAX_CACHE_SIZE ||
                                        unclipped.size.height > MAX_CACHE_SIZE;
 
                 // If we can't create a valid cache key for this descriptor (e.g.
                 // due to it referencing old non-interned style primitives), then
                 // don't try to cache it.
                 let has_valid_cache_key = self.surface_desc.is_some();
 
--- a/gfx/wr/webrender/src/platform/unix/font.rs
+++ b/gfx/wr/webrender/src/platform/unix/font.rs
@@ -81,25 +81,25 @@ pub fn unimplemented(error: FT_Error) ->
 macro_rules! ft_dyn_fn {
     ($func_name:ident($($arg_name:ident:$arg_type:ty),*) -> FT_Error) => {
         #[allow(non_snake_case)]
         unsafe fn $func_name($($arg_name:$arg_type),*) -> FT_Error {
             extern "C" fn unimpl_func($(_:$arg_type),*) -> FT_Error {
                 FT_Err_Unimplemented_Feature as FT_Error
             }
             lazy_static! {
-                static ref func: unsafe extern "C" fn($($arg_type),*) -> FT_Error = {
+                static ref FUNC: unsafe extern "C" fn($($arg_type),*) -> FT_Error = {
                     unsafe {
                         let cname = CString::new(stringify!($func_name)).unwrap();
                         let ptr = dlsym(RTLD_DEFAULT, cname.as_ptr());
                         if !ptr.is_null() { mem::transmute(ptr) } else { unimpl_func }
                     }
                 };
             }
-            (*func)($($arg_name),*)
+            (*FUNC)($($arg_name),*)
         }
     }
 }
 
 // On Android, just statically link in the symbols...
 #[cfg(all(target_os = "android", not(feature = "no_static_freetype")))]
 macro_rules! ft_dyn_fn {
     ($($proto:tt)+) => { extern "C" { fn $($proto)+; } }
--- a/gfx/wr/webrender/src/spatial_node.rs
+++ b/gfx/wr/webrender/src/spatial_node.rs
@@ -126,17 +126,17 @@ impl SpatialNode {
             LayoutFastTransform::identity, |perspective| perspective.into());
         let info = ReferenceFrameInfo {
             transform_style,
             source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
             source_perspective,
             origin_in_parent_reference_frame,
             invertible: true,
         };
-        Self::new(pipeline_id, parent_index, SpatialNodeType:: ReferenceFrame(info))
+        Self::new(pipeline_id, parent_index, SpatialNodeType::ReferenceFrame(info))
     }
 
     pub fn new_sticky_frame(
         parent_index: SpatialNodeIndex,
         sticky_frame_info: StickyFrameInfo,
         pipeline_id: PipelineId,
     ) -> Self {
         Self::new(pipeline_id, Some(parent_index), SpatialNodeType::StickyFrame(sticky_frame_info))
index 13b3419a39b2a752b261e019b6f9cb56715551bf..842a7a5e56f24c1c909c0a0ff9c1a62d424397de
GIT binary patch
literal 7442
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
--- a/js/src/gdb/mozilla/ExecutableAllocator.py
+++ b/js/src/gdb/mozilla/ExecutableAllocator.py
@@ -54,16 +54,19 @@ class jsjitExecutableAllocator(object):
             hashShift = allocator.value['m_pools']['mImpl']['mHashShift']
             self.max = 1 << (HASHNUMBER_BIT_SIZE - hashShift)
             if self.table == 0:
                 self.max = 0
 
         def __iter__(self):
             return self
 
+        def next(self):
+            return self.__next__()
+
         def __next__(self):
             cur = self.index
             if cur >= self.max:
                 raise StopIteration()
             self.index = self.index + 1
             if self.table[cur]['mKeyHash'] > 1:  # table[i]->isLive()
                 return self.table[cur]['mValueData'].cast(self.entryType)
             return self.__next__()
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1480390.js
@@ -0,0 +1,35 @@
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () {};");
+
+function* wrapNoThrow() {
+    let iter = {
+	[Symbol.iterator]() {
+            return this;
+	},
+	next() {
+            return { value: 10, done: false };
+	},
+	return() { return "invalid return value" }
+    };
+    for (const i of iter)
+	yield i;
+}
+
+function foo() {
+    for (var i of [1,2,3]) {
+	for (var j of [4,5,6]) {
+	    try {
+		for (const i of wrapNoThrow()) break;
+	    } catch (e) {}
+	}
+	for (var j of [7,8,9]) {
+	}
+    }
+}
+
+for (var i = 0; i < 10; i++) {
+    try {
+	foo();
+    } catch(e) {}
+}
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -468,31 +468,36 @@ static inline jsbytecode* GetNextNonLoop
     if (op == JSOP_LOOPENTRY) {
       *skippedLoopEntry = pc;
     }
     return GetNextPc(pc);
   }
   return pc;
 }
 
-static bool HasLiveStackValueAtDepth(JSScript* script, jsbytecode* pc,
-                                     uint32_t stackDepth) {
+class NoOpTryNoteFilter {
+ public:
+  explicit NoOpTryNoteFilter() = default;
+  bool operator()(const JSTryNote*) { return true; }
+};
+
+class TryNoteIterAll : public TryNoteIter<NoOpTryNoteFilter> {
+ public:
+  TryNoteIterAll(JSContext* cx, JSScript* script, jsbytecode* pc)
+      : TryNoteIter(cx, script, pc, NoOpTryNoteFilter()) {}
+};
+
+static bool HasLiveStackValueAtDepth(JSContext* cx, HandleScript script,
+                                     jsbytecode* pc, uint32_t stackDepth) {
   if (!script->hasTrynotes()) {
     return false;
   }
 
-  uint32_t pcOffset = script->pcToOffset(pc);
-
-  for (const JSTryNote& tn : script->trynotes()) {
-    if (pcOffset < tn.start) {
-      continue;
-    }
-    if (pcOffset >= tn.start + tn.length) {
-      continue;
-    }
+  for (TryNoteIterAll tni(cx, script, pc); !tni.done(); ++tni) {
+    const JSTryNote& tn = **tni;
 
     switch (tn.kind) {
       case JSTRY_FOR_IN:
         // For-in loops have only the iterator on stack.
         if (stackDepth == tn.stackDepth) {
           return true;
         }
         break;
@@ -1007,17 +1012,18 @@ static bool InitFromBailout(JSContext* c
       // the stack if we are at the newest frame.
       //
       // For instance, if calling |f()| pushed an Ion frame which threw,
       // the snapshot expects the return value to be pushed, but it's
       // possible nothing was pushed before we threw. We can't drop
       // iterators, however, so read them out. They will be closed by
       // HandleExceptionBaseline.
       MOZ_ASSERT(cx->realm()->isDebuggee());
-      if (iter.moreFrames() || HasLiveStackValueAtDepth(script, pc, i + 1)) {
+      if (iter.moreFrames() ||
+          HasLiveStackValueAtDepth(cx, script, pc, i + 1)) {
         v = iter.read();
       } else {
         iter.skip();
         v = MagicValue(JS_OPTIMIZED_OUT);
       }
     } else {
       v = iter.read();
     }
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -788,17 +788,18 @@ NS_DEFINE_STATIC_IID_ACCESSOR(HttpBaseCh
 // - We want to store member function pointer to call at resume time, but one
 //   such function--HandleAsyncAbort--we want to share between the
 //   nsHttpChannel/HttpChannelChild.  Can't define it in base class, because
 //   then we'd have to cast member function ptr between base/derived class
 //   types.  Sigh...
 template <class T>
 class HttpAsyncAborter {
  public:
-  explicit HttpAsyncAborter(T *derived) : mThis(derived), mCallOnResume(0) {}
+  explicit HttpAsyncAborter(T *derived)
+      : mThis(derived), mCallOnResume(nullptr) {}
 
   // Aborts channel: calls OnStart/Stop with provided status, removes channel
   // from loadGroup.
   MOZ_MUST_USE nsresult AsyncAbort(nsresult status);
 
   // Does most the actual work.
   void HandleAsyncAbort();
 
@@ -808,17 +809,17 @@ class HttpAsyncAborter {
   MOZ_MUST_USE virtual nsresult AsyncCall(
       void (T::*funcPtr)(), nsRunnableMethod<T> **retval = nullptr);
 
  private:
   T *mThis;
 
  protected:
   // Function to be called at resume time
-  void (T::*mCallOnResume)(void);
+  std::function<nsresult(T *)> mCallOnResume;
 };
 
 template <class T>
 MOZ_MUST_USE nsresult HttpAsyncAborter<T>::AsyncAbort(nsresult status) {
   MOZ_LOG(gHttpLog, LogLevel::Debug,
           ("HttpAsyncAborter::AsyncAbort [this=%p status=%" PRIx32 "]\n", mThis,
            static_cast<uint32_t>(status)));
 
@@ -833,17 +834,20 @@ MOZ_MUST_USE nsresult HttpAsyncAborter<T
 template <class T>
 inline void HttpAsyncAborter<T>::HandleAsyncAbort() {
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
 
   if (mThis->mSuspendCount) {
     MOZ_LOG(
         gHttpLog, LogLevel::Debug,
         ("Waiting until resume to do async notification [this=%p]\n", mThis));
-    mCallOnResume = &T::HandleAsyncAbort;
+    mCallOnResume = [](T *self) {
+      self->HandleAsyncAbort();
+      return NS_OK;
+    };
     return;
   }
 
   mThis->DoNotifyListener();
 
   // finally remove ourselves from the load group.
   if (mThis->mLoadGroup)
     mThis->mLoadGroup->RemoveRequest(mThis, nullptr, mThis->mStatus);
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -2342,19 +2342,27 @@ HttpChannelChild::Resume() {
   // Don't SendResume at all if we're diverting callbacks to the parent (unless
   // suspend was sent earlier); otherwise, resume will be called at the correct
   // time in the parent itself.
   if (!--mSuspendCount && (!mDivertingToParent || mSuspendSent)) {
     if (RemoteChannelExists()) {
       SendResume();
     }
     if (mCallOnResume) {
-      rv = AsyncCall(mCallOnResume);
-      NS_ENSURE_SUCCESS(rv, rv);
-      mCallOnResume = nullptr;
+      nsCOMPtr<nsIEventTarget> neckoTarget = GetNeckoTarget();
+      MOZ_ASSERT(neckoTarget);
+
+      RefPtr<HttpChannelChild> self = this;
+      std::function<nsresult(HttpChannelChild*)> callOnResume = nullptr;
+      std::swap(callOnResume, mCallOnResume);
+      rv = neckoTarget->Dispatch(
+          NS_NewRunnableFunction(
+              "net::HttpChannelChild::mCallOnResume",
+              [callOnResume, self{std::move(self)}]() { callOnResume(self); }),
+          NS_DISPATCH_NORMAL);
     }
   }
   if (mSynthesizedResponsePump) {
     mSynthesizedResponsePump->Resume();
   }
   mEventQ->Resume();
 
   return rv;
@@ -3808,17 +3816,17 @@ HttpChannelChild::LogMimeTypeMismatch(co
                                       const nsAString& aContentType) {
   RefPtr<Document> doc;
   if (mLoadInfo) {
     mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
   }
 
   nsAutoString url(aURL);
   nsAutoString contentType(aContentType);
-  const char16_t* params[] = { url.get(), contentType.get() };
+  const char16_t* params[] = {url.get(), contentType.get()};
   nsContentUtils::ReportToConsole(
       aWarning ? nsIScriptError::warningFlag : nsIScriptError::errorFlag,
       NS_LITERAL_CSTRING("MIMEMISMATCH"), doc,
       nsContentUtils::eSECURITY_PROPERTIES, nsCString(aMessageName).get(),
       params, ArrayLength(params));
   return NS_OK;
 }
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -434,47 +434,56 @@ nsresult nsHttpChannel::PrepareToConnect
   if (mCanceled) {
     return mStatus;
   }
 
   if (mSuspendCount) {
     // We abandon the connection here if there was one.
     LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
     MOZ_ASSERT(!mCallOnResume);
-    mCallOnResume = &nsHttpChannel::HandleOnBeforeConnect;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->HandleOnBeforeConnect();
+      return NS_OK;
+    };
     return NS_OK;
   }
 
   return OnBeforeConnect();
 }
 
 void nsHttpChannel::HandleContinueCancelledByTrackingProtection() {
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
 
   if (mSuspendCount) {
     LOG(
         ("Waiting until resume HandleContinueCancelledByTrackingProtection "
          "[this=%p]\n",
          this));
-    mCallOnResume = &nsHttpChannel::HandleContinueCancelledByTrackingProtection;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->HandleContinueCancelledByTrackingProtection();
+      return NS_OK;
+    };
     return;
   }
 
   LOG(("nsHttpChannel::HandleContinueCancelledByTrackingProtection [this=%p]\n",
        this));
   ContinueCancelledByTrackingProtection();
 }
 
 void nsHttpChannel::HandleOnBeforeConnect() {
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
   nsresult rv;
 
   if (mSuspendCount) {
     LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
-    mCallOnResume = &nsHttpChannel::HandleOnBeforeConnect;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->HandleOnBeforeConnect();
+      return NS_OK;
+    };
     return;
   }
 
   LOG(("nsHttpChannel::HandleOnBeforeConnect [this=%p]\n", this));
   rv = OnBeforeConnect();
   if (NS_FAILED(rv)) {
     CloseCacheEntry(false);
     Unused << AsyncAbort(rv);
@@ -588,30 +597,36 @@ nsresult nsHttpChannel::OnBeforeConnect(
   if (mCanceled) {
     return mStatus;
   }
 
   if (mSuspendCount) {
     // We abandon the connection here if there was one.
     LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
     MOZ_ASSERT(!mCallOnResume);
-    mCallOnResume = &nsHttpChannel::OnBeforeConnectContinue;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->OnBeforeConnectContinue();
+      return NS_OK;
+    };
     return NS_OK;
   }
 
   return Connect();
 }
 
 void nsHttpChannel::OnBeforeConnectContinue() {
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
   nsresult rv;
 
   if (mSuspendCount) {
     LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
-    mCallOnResume = &nsHttpChannel::OnBeforeConnectContinue;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->OnBeforeConnectContinue();
+      return NS_OK;
+    };
     return;
   }
 
   LOG(("nsHttpChannel::OnBeforeConnectContinue [this=%p]\n", this));
   rv = Connect();
   if (NS_FAILED(rv)) {
     CloseCacheEntry(false);
     Unused << AsyncAbort(rv);
@@ -769,24 +784,41 @@ nsresult nsHttpChannel::ContinueConnect(
   }
 
   if (mLoadFlags & LOAD_NO_NETWORK_IO) {
     LOG(("  mLoadFlags & LOAD_NO_NETWORK_IO"));
     return NS_ERROR_DOCUMENT_NOT_CACHED;
   }
 
   // hit the net...
+  return DoConnect();
+}
+
+nsresult nsHttpChannel::DoConnect(nsAHttpConnection *aConn) {
+  LOG(("nsHttpChannel::DoConnect [this=%p]\n", this));
+
   nsresult rv = SetupTransaction();
-  if (NS_FAILED(rv)) return rv;
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // transfer ownership of connection to transaction
+  if (aConn) {
+    mTransaction->SetConnection(aConn);
+  }
 
   rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
-  if (NS_FAILED(rv)) return rv;
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   rv = mTransactionPump->AsyncRead(this, nullptr);
-  if (NS_FAILED(rv)) return rv;
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   uint32_t suspendCount = mSuspendCount;
   if (mAsyncResumePending) {
     LOG(
         ("  Suspend()'ing transaction pump once because of async resume pending"
          ", sc=%u, pump=%p, this=%p",
          suspendCount, mTransactionPump.get(), this));
     ++suspendCount;
@@ -847,17 +879,20 @@ void nsHttpChannel::DoAsyncAbort(nsresul
   Unused << AsyncAbort(aStatus);
 }
 
 void nsHttpChannel::HandleAsyncRedirect() {
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
 
   if (mSuspendCount) {
     LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
-    mCallOnResume = &nsHttpChannel::HandleAsyncRedirect;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->HandleAsyncRedirect();
+      return NS_OK;
+    };
     return;
   }
 
   nsresult rv = NS_OK;
 
   LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
 
   // since this event is handled asynchronously, it is possible that this
@@ -913,17 +948,20 @@ nsresult nsHttpChannel::ContinueHandleAs
   return NS_OK;
 }
 
 void nsHttpChannel::HandleAsyncNotModified() {
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
 
   if (mSuspendCount) {
     LOG(("Waiting until resume to do async not-modified [this=%p]\n", this));
-    mCallOnResume = &nsHttpChannel::HandleAsyncNotModified;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->HandleAsyncNotModified();
+      return NS_OK;
+    };
     return;
   }
 
   LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
 
   DoNotifyListener();
 
   CloseCacheEntry(false);
@@ -933,17 +971,20 @@ void nsHttpChannel::HandleAsyncNotModifi
   if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
 }
 
 void nsHttpChannel::HandleAsyncFallback() {
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
 
   if (mSuspendCount) {
     LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
-    mCallOnResume = &nsHttpChannel::HandleAsyncFallback;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->HandleAsyncFallback();
+      return NS_OK;
+    };
     return;
   }
 
   nsresult rv = NS_OK;
 
   LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this));
 
   // since this event is handled asynchronously, it is possible that this
@@ -2317,17 +2358,20 @@ void nsHttpChannel::AsyncContinueProcess
 
 nsresult nsHttpChannel::ContinueProcessResponse1() {
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
   nsresult rv;
 
   if (mSuspendCount) {
     LOG(("Waiting until resume to finish processing response [this=%p]\n",
          this));
-    mCallOnResume = &nsHttpChannel::AsyncContinueProcessResponse;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->AsyncContinueProcessResponse();
+      return NS_OK;
+    };
     return NS_OK;
   }
 
   // Check if request was cancelled during http-on-examine-response.
   if (mCanceled) {
     return CallOnStartRequest();
   }
 
@@ -2411,19 +2455,16 @@ nsresult nsHttpChannel::ContinueProcessR
     // code that called this function.
     return NS_OK;
   }
 
   rv = NS_OK;
 
   uint32_t httpStatus = mResponseHead->Status();
 
-  bool successfulReval = false;
-  bool partialContentUsed = false;
-
   // handle different server response categories.  Note that we handle
   // caching or not caching of error pages in
   // nsHttpResponseHead::MustValidate; if you change this switch, update that
   // one
   switch (httpStatus) {
     case 200:
     case 203:
       // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
@@ -2437,20 +2478,26 @@ nsresult nsHttpChannel::ContinueProcessR
         break;
       }
       // these can normally be cached
       rv = ProcessNormal();
       MaybeInvalidateCacheEntryForSubsequentGet();
       break;
     case 206:
       if (mCachedContentIsPartial) {  // an internal byte range request...
-        rv = ProcessPartialContent();
-        if (NS_SUCCEEDED(rv)) {
-          partialContentUsed = true;
+        auto func = [](auto *self, nsresult aRv) {
+          return self->ContinueProcessResponseAfterPartialContent(aRv);
+        };
+        rv = ProcessPartialContent(func);
+        // Directly call ContinueProcessResponseAfterPartialContent if channel
+        // is not suspended or ProcessPartialContent throws.
+        if (!mSuspendCount || NS_FAILED(rv)) {
+          return ContinueProcessResponseAfterPartialContent(rv);
         }
+        return NS_OK;
       } else {
         mCacheInputStream.CloseAndRelease();
         rv = ProcessNormal();
       }
       break;
     case 300:
     case 301:
     case 302:
@@ -2475,39 +2522,26 @@ nsresult nsHttpChannel::ContinueProcessR
           DoNotifyListener();
         } else {
           rv = ContinueProcessResponse3(rv);
         }
       }
       break;
     case 304:
       if (!ShouldBypassProcessNotModified()) {
-        rv = ProcessNotModified();
-        if (NS_SUCCEEDED(rv)) {
-          successfulReval = true;
-          break;
+        auto func = [](auto *self, nsresult aRv) {
+          return self->ContinueProcessResponseAfterNotModified(aRv);
+        };
+        rv = ProcessNotModified(func);
+        // Directly call ContinueProcessResponseAfterNotModified if channel
+        // is not suspended or ProcessNotModified throws.
+        if (!mSuspendCount || NS_FAILED(rv)) {
+          return ContinueProcessResponseAfterNotModified(rv);
         }
-
-        LOG(("ProcessNotModified failed [rv=%" PRIx32 "]\n",
-             static_cast<uint32_t>(rv)));
-
-        // We cannot read from the cache entry, it might be in an
-        // incosistent state.  Doom it and redirect the channel
-        // to the same URI to reload from the network.
-        mCacheInputStream.CloseAndRelease();
-        if (mCacheEntry) {
-          mCacheEntry->AsyncDoom(nullptr);
-          mCacheEntry = nullptr;
-        }
-
-        rv = StartRedirectChannelToURI(mURI,
-                                       nsIChannelEventSink::REDIRECT_INTERNAL);
-        if (NS_SUCCEEDED(rv)) {
-          return NS_OK;
-        }
+        return NS_OK;
       }
 
       // Don't cache uninformative 304
       if (mCustomConditionalRequest) {
         CloseCacheEntry(false);
       }
 
       if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
@@ -2568,32 +2602,92 @@ nsresult nsHttpChannel::ContinueProcessR
       CloseCacheEntry(false);
       MOZ_FALLTHROUGH;  // process normally
     default:
       rv = ProcessNormal();
       MaybeInvalidateCacheEntryForSubsequentGet();
       break;
   }
 
+  UpdateCacheDisposition(false, false);
+  return rv;
+}
+
+nsresult nsHttpChannel::ContinueProcessResponseAfterPartialContent(
+    nsresult aRv) {
+  LOG(
+      ("nsHttpChannel::ContinueProcessResponseAfterPartialContent "
+       "[this=%p, rv=%" PRIx32 "]",
+       this, static_cast<uint32_t>(aRv)));
+
+  UpdateCacheDisposition(false, NS_SUCCEEDED(aRv));
+  return aRv;
+}
+
+nsresult nsHttpChannel::ContinueProcessResponseAfterNotModified(nsresult aRv) {
+  LOG(
+      ("nsHttpChannel::ContinueProcessResponseAfterNotModified "
+       "[this=%p, rv=%" PRIx32 "]",
+       this, static_cast<uint32_t>(aRv)));
+
+  if (NS_SUCCEEDED(aRv)) {
+    mTransactionReplaced = true;
+    UpdateCacheDisposition(true, false);
+    return NS_OK;
+  }
+
+  LOG(("ProcessNotModified failed [rv=%" PRIx32 "]\n",
+       static_cast<uint32_t>(aRv)));
+
+  // We cannot read from the cache entry, it might be in an
+  // incosistent state.  Doom it and redirect the channel
+  // to the same URI to reload from the network.
+  mCacheInputStream.CloseAndRelease();
+  if (mCacheEntry) {
+    mCacheEntry->AsyncDoom(nullptr);
+    mCacheEntry = nullptr;
+  }
+
+  nsresult rv =
+      StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+  if (NS_SUCCEEDED(rv)) {
+    return NS_OK;
+  }
+
+  // Don't cache uninformative 304
+  if (mCustomConditionalRequest) {
+    CloseCacheEntry(false);
+  }
+
+  if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
+    rv = ProcessNormal();
+  }
+
+  UpdateCacheDisposition(false, false);
+  return rv;
+}
+
+void nsHttpChannel::UpdateCacheDisposition(bool aSuccessfulReval,
+                                           bool aPartialContentUsed) {
   if (mRaceDelay && !mRaceCacheWithNetwork &&
       (mCachedContentIsPartial || mDidReval)) {
-    if (successfulReval || partialContentUsed) {
+    if (aSuccessfulReval || aPartialContentUsed) {
       AccumulateCategorical(
           Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentUsed);
     } else {
       AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::
                                 CachedContentNotUsed);
     }
   }
 
   if (Telemetry::CanRecordPrereleaseData()) {
     CacheDisposition cacheDisposition;
     if (!mDidReval) {
       cacheDisposition = kCacheMissed;
-    } else if (successfulReval) {
+    } else if (aSuccessfulReval) {
       cacheDisposition = kCacheHitViaReval;
     } else {
       cacheDisposition = kCacheMissedViaReval;
     }
     AccumulateCacheHitTelemetry(cacheDisposition);
     mCacheDisposition = cacheDisposition;
 
     Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
@@ -2607,17 +2701,16 @@ nsresult nsHttpChannel::ContinueProcessR
         v09Info += 1;
       }
       if (mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort()) {
         v09Info += 2;
       }
       Telemetry::Accumulate(Telemetry::HTTP_09_INFO, v09Info);
     }
   }
-  return rv;
 }
 
 nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
   bool doNotRender = DoNotRender3xxBody(rv);
 
   if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
     bool isHTTP = false;
     if (NS_FAILED(mRedirectURI->SchemeIs("http", &isHTTP))) isHTTP = false;
@@ -2812,17 +2905,20 @@ nsresult nsHttpChannel::ProxyFailover() 
 }
 
 void nsHttpChannel::HandleAsyncRedirectChannelToHttps() {
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
 
   if (mSuspendCount) {
     LOG(("Waiting until resume to do async redirect to https [this=%p]\n",
          this));
-    mCallOnResume = &nsHttpChannel::HandleAsyncRedirectChannelToHttps;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->HandleAsyncRedirectChannelToHttps();
+      return NS_OK;
+    };
     return;
   }
 
   nsresult rv = StartRedirectChannelToHttps();
   if (NS_FAILED(rv)) {
     rv = ContinueAsyncRedirectChannelToURI(rv);
     if (NS_FAILED(rv)) {
       LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
@@ -2844,17 +2940,20 @@ nsresult nsHttpChannel::StartRedirectCha
 }
 
 void nsHttpChannel::HandleAsyncAPIRedirect() {
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
   MOZ_ASSERT(mAPIRedirectToURI, "How did that happen?");
 
   if (mSuspendCount) {
     LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
-    mCallOnResume = &nsHttpChannel::HandleAsyncAPIRedirect;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->HandleAsyncAPIRedirect();
+      return NS_OK;
+    };
     return;
   }
 
   nsresult rv = StartRedirectChannelToURI(
       mAPIRedirectToURI, nsIChannelEventSink::REDIRECT_PERMANENT);
   if (NS_FAILED(rv)) {
     rv = ContinueAsyncRedirectChannelToURI(rv);
     if (NS_FAILED(rv)) {
@@ -3212,17 +3311,19 @@ nsresult nsHttpChannel::SetupByteRangeRe
 void nsHttpChannel::UntieByteRangeRequest() {
   DebugOnly<nsresult> rv;
   rv = mRequestHead.ClearHeader(nsHttp::Range);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
   rv = mRequestHead.ClearHeader(nsHttp::If_Range);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
-nsresult nsHttpChannel::ProcessPartialContent() {
+nsresult nsHttpChannel::ProcessPartialContent(
+    const std::function<nsresult(nsHttpChannel *, nsresult)>
+        &aContinueProcessResponseFunc) {
   // ok, we've just received a 206
   //
   // we need to stream whatever data is in the cache out first, and then
   // pick up whatever data is on the wire, writing it into the cache.
 
   LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));
 
   NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
@@ -3312,25 +3413,26 @@ nsresult nsHttpChannel::ProcessPartialCo
   gHttpHandler->OnExamineMergedResponse(this);
 
   if (mConcurrentCacheAccess) {
     mCachedContentIsPartial = false;
     // Leave the mConcurrentCacheAccess flag set, we want to use it
     // to prevent duplicate OnStartRequest call on the target listener
     // in case this channel is canceled before it gets its OnStartRequest
     // from the http transaction.
-
-    // Now we continue reading the network response.
-  } else {
-    // the cached content is valid, although incomplete.
-    mCachedContentIsValid = true;
-    rv = ReadFromCache(false);
-  }
-
-  return rv;
+    return rv;
+  }
+
+  // Now we continue reading the network response.
+  // the cached content is valid, although incomplete.
+  mCachedContentIsValid = true;
+  return CallOrWaitForResume([aContinueProcessResponseFunc](auto *self) {
+    nsresult rv = self->ReadFromCache(false);
+    return aContinueProcessResponseFunc(self, rv);
+  });
 }
 
 nsresult nsHttpChannel::OnDoneReadingPartialCacheEntry(bool *streamDone) {
   nsresult rv;
 
   LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
 
   // by default, assume we would have streamed all data or failed...
@@ -3386,17 +3488,19 @@ bool nsHttpChannel::ShouldBypassProcessN
         ("Server returned a 304 response even though we did not send a "
          "conditional request"));
     return true;
   }
 
   return false;
 }
 
-nsresult nsHttpChannel::ProcessNotModified() {
+nsresult nsHttpChannel::ProcessNotModified(
+    const std::function<nsresult(nsHttpChannel *, nsresult)>
+        &aContinueProcessResponseFunc) {
   nsresult rv;
 
   LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));
 
   // Assert ShouldBypassProcessNotModified() has been checked before call to
   // ProcessNotModified().
   MOZ_ASSERT(!ShouldBypassProcessNotModified());
 
@@ -3456,21 +3560,20 @@ nsresult nsHttpChannel::ProcessNotModifi
   gHttpHandler->OnExamineMergedResponse(this);
 
   mCachedContentIsValid = true;
 
   // Tell other consumers the entry is OK to use
   rv = mCacheEntry->SetValid();
   if (NS_FAILED(rv)) return rv;
 
-  rv = ReadFromCache(false);
-  if (NS_FAILED(rv)) return rv;
-
-  mTransactionReplaced = true;
-  return NS_OK;
+  return CallOrWaitForResume([aContinueProcessResponseFunc](auto *self) {
+    nsresult rv = self->ReadFromCache(false);
+    return aContinueProcessResponseFunc(self, rv);
+  });
 }
 
 nsresult nsHttpChannel::ProcessFallback(bool *waitingForRedirectCallback) {
   LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
   nsresult rv;
 
   *waitingForRedirectCallback = false;
   mFallingBack = false;
@@ -5910,17 +6013,20 @@ nsHttpChannel::CancelForTrackingProtecti
   if (mCanceled) {
     return mStatus;
   }
 
   if (mSuspendCount) {
     LOG(("Waiting until resume in Cancel [this=%p]\n", this));
     MOZ_ASSERT(!mCallOnResume);
     mTrackingProtectionCancellationPending = 1;
-    mCallOnResume = &nsHttpChannel::HandleContinueCancelledByTrackingProtection;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->HandleContinueCancelledByTrackingProtection();
+      return NS_OK;
+    };
     return NS_OK;
   }
 
   // Check to see if we should redirect this channel elsewhere by
   // nsIHttpChannel.redirectTo API request
   if (mAPIRedirectToURI) {
     mTrackingProtectionCancellationPending = 1;
     return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
@@ -6493,22 +6599,16 @@ nsresult nsHttpChannel::BeginConnectActu
     return mStatus;
   }
 
   if (mTrackingProtectionCancellationPending) {
     LOG(
         ("Waiting for tracking protection cancellation in BeginConnectActual "
          "[this=%p]\n",
          this));
-    MOZ_ASSERT(
-        !mCallOnResume ||
-            mCallOnResume ==
-                &nsHttpChannel::HandleContinueCancelledByTrackingProtection,
-        "We should be paused waiting for cancellation from tracking "
-        "protection");
     return NS_OK;
   }
 
   MaybeStartDNSPrefetch();
 
   nsresult rv = ContinueBeginConnectWithResult();
   if (NS_FAILED(rv)) {
     return rv;
@@ -6640,17 +6740,20 @@ nsHttpChannel::SetPriority(int32_t value
 nsresult nsHttpChannel::ContinueBeginConnectWithResult() {
   LOG(("nsHttpChannel::ContinueBeginConnectWithResult [this=%p]", this));
   MOZ_ASSERT(!mCallOnResume, "How did that happen?");
 
   nsresult rv;
 
   if (mSuspendCount) {
     LOG(("Waiting until resume to do async connect [this=%p]\n", this));
-    mCallOnResume = &nsHttpChannel::ContinueBeginConnect;
+    mCallOnResume = [](nsHttpChannel *self) {
+      self->ContinueBeginConnect();
+      return NS_OK;
+    };
     rv = NS_OK;
   } else if (mCanceled) {
     // We may have been cancelled already, by nsChannelClassifier in that
     // case, we should not send the request to the server
     rv = mStatus;
   } else {
     rv = PrepareToConnect();
   }
@@ -7409,65 +7512,102 @@ nsHttpChannel::OnStopRequest(nsIRequest 
           mLastStatusReported, TimeStamp::Now(), mLogicalOffset,
           mCacheDisposition, &mTransactionTimings, nullptr);
     }
 #endif
 
     // handle auth retry...
     if (authRetry) {
       mAuthRetryPending = false;
-      status = DoAuthRetry(conn);
-      if (NS_SUCCEEDED(status)) return NS_OK;
-    }
-
-    // If DoAuthRetry failed, or if we have been cancelled since showing
-    // the auth. dialog, then we need to send OnStartRequest now
-    if (authRetry || (mAuthRetryPending && NS_FAILED(status))) {
-      MOZ_ASSERT(NS_FAILED(status), "should have a failure code here");
-      // NOTE: since we have a failure status, we can ignore the return
-      // value from onStartRequest.
-      LOG(("  calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
-           mListener.get()));
-      if (mListener) {
-        MOZ_ASSERT(!mOnStartRequestCalled,
-                   "We should not call OnStartRequest twice.");
-        mListener->OnStartRequest(this, mListenerContext);
-        mOnStartRequestCalled = true;
-      } else {
-        NS_WARNING("OnStartRequest skipped because of null listener");
+      auto continueOSR = [authRetry, isFromNet, contentComplete,
+                          stickyConn{std::move(stickyConn)}](auto *self,
+                                                             nsresult aStatus) {
+        return self->ContinueOnStopRequestAfterAuthRetry(
+            aStatus, authRetry, isFromNet, contentComplete, stickyConn);
+      };
+      status = DoAuthRetry(conn, continueOSR);
+      if (NS_SUCCEEDED(status)) {
+        return NS_OK;
       }
     }
-
-    // if this transaction has been replaced, then bail.
-    if (mTransactionReplaced) {
-      LOG(("Transaction replaced\n"));
-      // This was just the network check for a 304 response.
-      mFirstResponseSource = RESPONSE_PENDING;
-      return NS_OK;
-    }
-
-    bool upgradeWebsocket = mUpgradeProtocolCallback && stickyConn &&
-                            mResponseHead &&
-                            ((mResponseHead->Status() == 101 &&
-                              mResponseHead->Version() == HttpVersion::v1_1) ||
-                             (mResponseHead->Status() == 200 &&
-                              mResponseHead->Version() == HttpVersion::v2_0));
-
-    bool upgradeConnect = mUpgradeProtocolCallback && stickyConn &&
-                          (mCaps & NS_HTTP_CONNECT_ONLY) && mResponseHead &&
-                          mResponseHead->Status() == 200;
-
-    if (upgradeWebsocket || upgradeConnect) {
-      nsresult rv = gHttpHandler->ConnMgr()->CompleteUpgrade(
-          stickyConn, mUpgradeProtocolCallback);
-      if (NS_FAILED(rv)) {
-        LOG(("  CompleteUpgrade failed with %08x", static_cast<uint32_t>(rv)));
-      }
-    }
-  }
+    return ContinueOnStopRequestAfterAuthRetry(status, authRetry, isFromNet,
+                                               contentComplete, stickyConn);
+  }
+
+  return ContinueOnStopRequest(status, isFromNet, contentComplete);
+}
+
+nsresult nsHttpChannel::ContinueOnStopRequestAfterAuthRetry(
+    nsresult aStatus, bool aAuthRetry, bool aIsFromNet, bool aContentComplete,
+    nsAHttpConnection *aStickyConn) {
+  LOG(
+      ("nsHttpChannel::ContinueOnStopRequestAfterAuthRetry "
+       "[this=%p, aStatus=%" PRIx32
+       " aAuthRetry=%d, aIsFromNet=%d, aStickyConn=%p]\n",
+       this, static_cast<uint32_t>(aStatus), aAuthRetry, aIsFromNet,
+       aStickyConn));
+
+  if (aAuthRetry && NS_SUCCEEDED(aStatus)) {
+    return NS_OK;
+  }
+
+  // If DoAuthRetry failed, or if we have been cancelled since showing
+  // the auth. dialog, then we need to send OnStartRequest now
+  if (aAuthRetry || (mAuthRetryPending && NS_FAILED(aStatus))) {
+    MOZ_ASSERT(NS_FAILED(aStatus), "should have a failure code here");
+    // NOTE: since we have a failure status, we can ignore the return
+    // value from onStartRequest.
+    LOG(("  calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
+         mListener.get()));
+    if (mListener) {
+      MOZ_ASSERT(!mOnStartRequestCalled,
+                 "We should not call OnStartRequest twice.");
+      mListener->OnStartRequest(this, mListenerContext);
+      mOnStartRequestCalled = true;
+    } else {
+      NS_WARNING("OnStartRequest skipped because of null listener");
+    }
+  }
+
+  // if this transaction has been replaced, then bail.
+  if (mTransactionReplaced) {
+    LOG(("Transaction replaced\n"));
+    // This was just the network check for a 304 response.
+    mFirstResponseSource = RESPONSE_PENDING;
+    return NS_OK;
+  }
+
+  bool upgradeWebsocket = mUpgradeProtocolCallback && aStickyConn &&
+                          mResponseHead &&
+                          ((mResponseHead->Status() == 101 &&
+                            mResponseHead->Version() == HttpVersion::v1_1) ||
+                           (mResponseHead->Status() == 200 &&
+                            mResponseHead->Version() == HttpVersion::v2_0));
+
+  bool upgradeConnect = mUpgradeProtocolCallback && aStickyConn &&
+                        (mCaps & NS_HTTP_CONNECT_ONLY) && mResponseHead &&
+                        mResponseHead->Status() == 200;
+
+  if (upgradeWebsocket || upgradeConnect) {
+    nsresult rv = gHttpHandler->ConnMgr()->CompleteUpgrade(
+        aStickyConn, mUpgradeProtocolCallback);
+    if (NS_FAILED(rv)) {
+      LOG(("  CompleteUpgrade failed with %08x", static_cast<uint32_t>(rv)));
+    }
+  }
+
+  return ContinueOnStopRequest(aStatus, aIsFromNet, aContentComplete);
+}
+
+nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet,
+                                              bool aContentComplete) {
+  LOG(
+      ("nsHttpChannel::ContinueOnStopRequest "
+       "[this=%p aStatus=%" PRIx32 ", aIsFromNet=%d]\n",
+       this, static_cast<uint32_t>(aStatus), aIsFromNet));
 
   // HTTP_CHANNEL_DISPOSITION TELEMETRY
   enum ChannelDisposition {
     kHttpCanceled = 0,
     kHttpDisk = 1,
     kHttpNetOK = 2,
     kHttpNetEarlyFail = 3,
     kHttpNetLateFail = 4,
@@ -7487,17 +7627,17 @@ nsHttpChannel::OnStopRequest(nsIRequest 
     chanDisposition = kHttpCanceled;
     upgradeChanDisposition =
         Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::cancel;
   } else if (!mUsedNetwork || (mRaceCacheWithNetwork &&
                                mFirstResponseSource == RESPONSE_FROM_CACHE)) {
     chanDisposition = kHttpDisk;
     upgradeChanDisposition =
         Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::disk;
-  } else if (NS_SUCCEEDED(status) && mResponseHead &&
+  } else if (NS_SUCCEEDED(aStatus) && mResponseHead &&
              mResponseHead->Version() != HttpVersion::v0_9) {
     chanDisposition = kHttpNetOK;
     upgradeChanDisposition =
         Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netOk;
   } else if (!mTransferSize) {
     chanDisposition = kHttpNetEarlyFail;
     upgradeChanDisposition =
         Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netEarlyFail;
@@ -7535,17 +7675,17 @@ nsHttpChannel::OnStopRequest(nsIRequest 
                      : NS_LITERAL_CSTRING("disabledWont");
   }
   Telemetry::AccumulateCategoricalKeyed(upgradeKey, upgradeChanDisposition);
   LOG(("  nsHttpChannel::OnStopRequest ChannelDisposition %d\n",
        chanDisposition));
   Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_DISPOSITION, chanDisposition);
 
   // if needed, check cache entry has all data we expect
-  if (mCacheEntry && mCachePump && mConcurrentCacheAccess && contentComplete) {
+  if (mCacheEntry && mCachePump && mConcurrentCacheAccess && aContentComplete) {
     int64_t size, contentLength;
     nsresult rv = CheckPartial(mCacheEntry, &size, &contentLength);
     if (NS_SUCCEEDED(rv)) {
       if (size == int64_t(-1)) {
         // mayhemer TODO - we have to restart read from cache here at the size
         // offset
         MOZ_ASSERT(false);
         LOG(
@@ -7566,75 +7706,77 @@ nsHttpChannel::OnStopRequest(nsIRequest 
           rv = ContinueConnect();
           if (NS_SUCCEEDED(rv)) {
             LOG(("  performing range request"));
             mCachePump = nullptr;
             return NS_OK;
           }
           LOG(("  but range request perform failed 0x%08" PRIx32,
                static_cast<uint32_t>(rv)));
-          status = NS_ERROR_NET_INTERRUPT;
+          aStatus = NS_ERROR_NET_INTERRUPT;
         } else {
           LOG(("  but range request setup failed rv=0x%08" PRIx32
                ", failing load",
                static_cast<uint32_t>(rv)));
         }
       }
     }
   }
 
   mIsPending = false;
-  mStatus = status;
+  mStatus = aStatus;
 
   // perform any final cache operations before we close the cache entry.
   if (mCacheEntry && mRequestTimeInitialized) {
     bool writeAccess;
     // New implementation just returns value of the !mCacheEntryIsReadOnly flag
     // passed in. Old implementation checks on nsICache::ACCESS_WRITE flag.
     mCacheEntry->HasWriteAccess(!mCacheEntryIsReadOnly, &writeAccess);
     if (writeAccess) {
       nsresult rv = FinalizeCacheEntry();
       if (NS_FAILED(rv)) {
         LOG(("FinalizeCacheEntry failed (%08x)", static_cast<uint32_t>(rv)));
       }
     }
   }
 
-  ReportRcwnStats(isFromNet);
+  ReportRcwnStats(aIsFromNet);
 
   // Register entry to the PerformanceStorage resource timing
   MaybeReportTimingData();
 
   if (mListener) {
     LOG(("nsHttpChannel %p calling OnStopRequest\n", this));
     MOZ_ASSERT(mOnStartRequestCalled,
                "OnStartRequest should be called before OnStopRequest");
     MOZ_ASSERT(!mOnStopRequestCalled, "We should not call OnStopRequest twice");
-    mListener->OnStopRequest(this, mListenerContext, status);
+    mListener->OnStopRequest(this, mListenerContext, aStatus);
     mOnStopRequestCalled = true;
   }
 
   // notify "http-on-stop-connect" observers
   gHttpHandler->OnStopRequest(this);
 
   RemoveAsNonTailRequest();
 
   // If a preferred alt-data type was set, this signals the consumer is
   // interested in reading and/or writing the alt-data representation.
   // We need to hold a reference to the cache entry in case the listener calls
   // openAlternativeOutputStream() after CloseCacheEntry() clears mCacheEntry.
   if (!mPreferredCachedAltDataTypes.IsEmpty()) {
     mAltDataCacheEntry = mCacheEntry;
   }
 
-  CloseCacheEntry(!contentComplete);
+  CloseCacheEntry(!aContentComplete);
 
   if (mOfflineCacheEntry) CloseOfflineCacheEntry();
 
-  if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, status);
+  if (mLoadGroup) {
+    mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+  }
 
   // We don't need this info anymore
   CleanRedirectCacheChainIfNecessary();
 
   ReleaseListeners();
 
   return NS_OK;
 }
@@ -8190,88 +8332,90 @@ nsHttpChannel::ResumeAt(uint64_t aStartP
   LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%" PRIu64 " id='%s']\n", this,
        aStartPos, PromiseFlatCString(aEntityID).get()));
   mEntityID = aEntityID;
   mStartPos = aStartPos;
   mResuming = true;
   return NS_OK;
 }
 
-nsresult nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn) {
+nsresult nsHttpChannel::DoAuthRetry(
+    nsAHttpConnection *conn,
+    const std::function<nsresult(nsHttpChannel *, nsresult)>
+        &aContinueOnStopRequestFunc) {
   LOG(("nsHttpChannel::DoAuthRetry [this=%p]\n", this));
 
   MOZ_ASSERT(!mTransaction, "should not have a transaction");
-  nsresult rv;
-
-  // toggle mIsPending to allow nsIObserver implementations to modify
-  // the request headers (bug 95044).
-  mIsPending = false;
+
+  // Note that we don't have to toggle |mIsPending| anymore. See the reasons
+  // below.
+  // 1. We can't suspend the channel during "http-on-modify-request"
+  // when |mIsPending| is false.
+  // 2. We don't check |mIsPending| in SetRequestHeader now.
 
   // Reset mRequestObserversCalled because we've probably called the request
   // observers once already.
   mRequestObserversCalled = false;
 
   // fetch cookies, and add them to the request header.
   // the server response could have included cookies that must be sent with
   // this authentication attempt (bug 84794).
   // TODO: save cookies from auth response and send them here (bug 572151).
   AddCookiesToRequest();
 
   // notify "http-on-modify-request" observers
   CallOnModifyRequestObservers();
 
+  RefPtr<nsAHttpConnection> connRef(conn);
+  return CallOrWaitForResume(
+      [conn{std::move(connRef)}, aContinueOnStopRequestFunc](auto *self) {
+        return self->ContinueDoAuthRetry(conn, aContinueOnStopRequestFunc);
+      });
+}
+
+nsresult nsHttpChannel::ContinueDoAuthRetry(
+    nsAHttpConnection *aConn,
+    const std::function<nsresult(nsHttpChannel *, nsresult)>
+        &aContinueOnStopRequestFunc) {
+  LOG(("nsHttpChannel::ContinueDoAuthRetry [this=%p]\n", this));
+
   mIsPending = true;
 
   // get rid of the old response headers
   mResponseHead = nullptr;
 
   // rewind the upload stream
   if (mUploadStream) {
     nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
-    if (seekable) seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+    if (seekable) {
+      seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+    }
   }
 
   // always set sticky connection flag
   mCaps |= NS_HTTP_STICKY_CONNECTION;
   // and when needed, allow restart regardless the sticky flag
   if (mAuthConnectionRestartable) {
     LOG(("  connection made restartable"));
     mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
     mAuthConnectionRestartable = false;
   } else {
     LOG(("  connection made non-restartable"));
     mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
   }
 
-  // and create a new one...
-  rv = SetupTransaction();
-  if (NS_FAILED(rv)) return rv;
-
-  // transfer ownership of connection to transaction
-  if (conn) mTransaction->SetConnection(conn);
-
-  rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
-  if (NS_FAILED(rv)) return rv;
-
-  rv = mTransactionPump->AsyncRead(this, nullptr);
-  if (NS_FAILED(rv)) return rv;
-
-  uint32_t suspendCount = mSuspendCount;
-  if (mAsyncResumePending) {
-    LOG(
-        ("  Suspend()'ing transaction pump once because of async resume pending"
-         ", sc=%u, pump=%p, this=%p",
-         suspendCount, mTransactionPump.get(), this));
-    ++suspendCount;
-  }
-  while (suspendCount--) {
-    mTransactionPump->Suspend();
-  }
-
-  return NS_OK;
+  // notify "http-on-before-connect" observers
+  gHttpHandler->OnBeforeConnect(this);
+
+  RefPtr<nsAHttpConnection> connRef(aConn);
+  return CallOrWaitForResume(
+      [conn{std::move(connRef)}, aContinueOnStopRequestFunc](auto *self) {
+        nsresult rv = self->DoConnect(conn);
+        return aContinueOnStopRequestFunc(self, rv);
+      });
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIApplicationCacheChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::GetApplicationCache(nsIApplicationCache **out) {
@@ -8804,16 +8948,33 @@ nsHttpChannel::SuspendInternal() {
   nsresult rvCache = NS_OK;
   if (mCachePump) {
     rvCache = mCachePump->Suspend();
   }
 
   return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
 }
 
+nsresult nsHttpChannel::CallOrWaitForResume(
+    const std::function<nsresult(nsHttpChannel *)> &aFunc) {
+  if (mCanceled) {
+    MOZ_ASSERT(NS_FAILED(mStatus));
+    return mStatus;
+  }
+
+  if (mSuspendCount) {
+    LOG(("Waiting until resume [this=%p]\n", this));
+    MOZ_ASSERT(!mCallOnResume);
+    mCallOnResume = aFunc;
+    return NS_OK;
+  }
+
+  return aFunc(this);
+}
+
 NS_IMETHODIMP
 nsHttpChannel::ResumeInternal() {
   NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
 
   LOG(("nsHttpChannel::ResumeInternal [this=%p]\n", this));
 
   if (--mSuspendCount == 0) {
     mSuspendTotalTime +=
@@ -8823,30 +8984,34 @@ nsHttpChannel::ResumeInternal() {
       // Resume the interrupted procedure first, then resume
       // the pump to continue process the input stream.
       // Any newly created pump MUST be suspended to prevent calling
       // its OnStartRequest before OnStopRequest of any pre-existing
       // pump.  mAsyncResumePending ensures that.
       MOZ_ASSERT(!mAsyncResumePending);
       mAsyncResumePending = 1;
 
-      auto const callOnResume = mCallOnResume;
-      mCallOnResume = nullptr;
+      std::function<nsresult(nsHttpChannel *)> callOnResume = nullptr;
+      std::swap(callOnResume, mCallOnResume);
 
       RefPtr<nsHttpChannel> self(this);
       RefPtr<nsInputStreamPump> transactionPump = mTransactionPump;
       RefPtr<nsInputStreamPump> cachePump = mCachePump;
 
       nsresult rv = NS_DispatchToCurrentThread(NS_NewRunnableFunction(
           "nsHttpChannel::CallOnResume",
-          [callOnResume, self{std::move(self)},
+          [callOnResume{std::move(callOnResume)}, self{std::move(self)},
            transactionPump{std::move(transactionPump)},
            cachePump{std::move(cachePump)}]() {
             MOZ_ASSERT(self->mAsyncResumePending);
-            (self->*callOnResume)();
+            nsresult rv = self->CallOrWaitForResume(callOnResume);
+            if (NS_FAILED(rv)) {
+              self->CloseCacheEntry(false);
+              Unused << self->AsyncAbort(rv);
+            }
             MOZ_ASSERT(self->mAsyncResumePending);
 
             self->mAsyncResumePending = 0;
 
             // And now actually resume the previously existing pumps.
             if (transactionPump) {
               LOG(
                   ("nsHttpChannel::CallOnResume resuming previous transaction "
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -296,16 +296,22 @@ class nsHttpChannel final : public HttpB
   CacheDisposition mCacheDisposition;
 
  protected:
   virtual ~nsHttpChannel();
 
  private:
   typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
 
+  // Directly call |aFunc| if the channel is not canceled and not suspended.
+  // Otherwise, set |aFunc| to |mCallOnResume| and wait until the channel
+  // resumes.
+  nsresult CallOrWaitForResume(
+      const std::function<nsresult(nsHttpChannel *)> &aFunc);
+
   bool RequestIsConditional();
   void HandleContinueCancelledByTrackingProtection();
   nsresult CancelInternal(nsresult status);
   void ContinueCancelledByTrackingProtection();
 
   // Connections will only be established in this function.
   // (including DNS prefetch and speculative connection.)
   nsresult BeginConnectActual();
@@ -326,22 +332,27 @@ class nsHttpChannel final : public HttpB
   MOZ_MUST_USE nsresult Connect();
   void SpeculativeConnect();
   MOZ_MUST_USE nsresult SetupTransaction();
   MOZ_MUST_USE nsresult CallOnStartRequest();
   MOZ_MUST_USE nsresult ProcessResponse();
   void AsyncContinueProcessResponse();
   MOZ_MUST_USE nsresult ContinueProcessResponse1();
   MOZ_MUST_USE nsresult ContinueProcessResponse2(nsresult);
+  void UpdateCacheDisposition(bool aSuccessfulReval, bool aPartialContentUsed);
   MOZ_MUST_USE nsresult ContinueProcessResponse3(nsresult);
   MOZ_MUST_USE nsresult ProcessNormal();
   MOZ_MUST_USE nsresult ContinueProcessNormal(nsresult);
   void ProcessAltService();
   bool ShouldBypassProcessNotModified();
-  MOZ_MUST_USE nsresult ProcessNotModified();
+  MOZ_MUST_USE nsresult ProcessNotModified(
+      const std::function<nsresult(nsHttpChannel *, nsresult)>
+          &aContinueProcessResponseFunc);
+  MOZ_MUST_USE nsresult ContinueProcessResponseAfterNotModified(nsresult aRv);
+
   MOZ_MUST_USE nsresult AsyncProcessRedirection(uint32_t httpStatus);
   MOZ_MUST_USE nsresult ContinueProcessRedirection(nsresult);
   MOZ_MUST_USE nsresult ContinueProcessRedirectionAfterFallback(nsresult);
   MOZ_MUST_USE nsresult ProcessFailedProxyConnect(uint32_t httpStatus);
   MOZ_MUST_USE nsresult ProcessFallback(bool *waitingForRedirectCallback);
   MOZ_MUST_USE nsresult ContinueProcessFallback(nsresult);
   void HandleAsyncAbort();
   MOZ_MUST_USE nsresult EnsureAssocReq();
@@ -403,20 +414,35 @@ class nsHttpChannel final : public HttpB
   MOZ_MUST_USE nsresult InstallOfflineCacheListener(int64_t offset = 0);
   void MaybeInvalidateCacheEntryForSubsequentGet();
   void AsyncOnExamineCachedResponse();
 
   // Handle the bogus Content-Encoding Apache sometimes sends
   void ClearBogusContentEncodingIfNeeded();
 
   // byte range request specific methods
-  MOZ_MUST_USE nsresult ProcessPartialContent();
+  MOZ_MUST_USE nsresult ProcessPartialContent(
+      const std::function<nsresult(nsHttpChannel *, nsresult)>
+          &aContinueProcessResponseFunc);
+  MOZ_MUST_USE nsresult
+  ContinueProcessResponseAfterPartialContent(nsresult aRv);
   MOZ_MUST_USE nsresult OnDoneReadingPartialCacheEntry(bool *streamDone);
 
-  MOZ_MUST_USE nsresult DoAuthRetry(nsAHttpConnection *);
+  MOZ_MUST_USE nsresult
+  DoAuthRetry(nsAHttpConnection *,
+              const std::function<nsresult(nsHttpChannel *, nsresult)> &aOuter);
+  MOZ_MUST_USE nsresult ContinueDoAuthRetry(
+      nsAHttpConnection *aConn,
+      const std::function<nsresult(nsHttpChannel *, nsresult)> &aOuter);
+  MOZ_MUST_USE nsresult DoConnect(nsAHttpConnection *aConn = nullptr);
+  MOZ_MUST_USE nsresult ContinueOnStopRequestAfterAuthRetry(
+      nsresult aStatus, bool aAuthRetry, bool aIsFromNet, bool aContentComplete,
+      nsAHttpConnection *aStickyConn);
+  MOZ_MUST_USE nsresult ContinueOnStopRequest(nsresult status, bool aIsFromNet,
+                                              bool aContentComplete);
 
   void HandleAsyncRedirectChannelToHttps();
   MOZ_MUST_USE nsresult StartRedirectChannelToHttps();
   MOZ_MUST_USE nsresult ContinueAsyncRedirectChannelToURI(nsresult rv);
   MOZ_MUST_USE nsresult OpenRedirectChannel(nsresult rv);
 
   /**
    * A function that takes care of reading STS and PKP headers and enforcing
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_authRetry.js
@@ -0,0 +1,274 @@
+// This file tests async handling of a channel suspended in DoAuthRetry
+// notifying http-on-modify-request and http-on-before-connect observers.
+
+var CC = Components.Constructor;
+
+ChromeUtils.import("resource://testing-common/httpd.js");
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+// Turn off the authentication dialog blocking for this test.
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+              getService(Ci.nsIPrefBranch);
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+  return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+  return httpserv.identity.primaryPort;
+});
+
+var obs = Cc["@mozilla.org/observer-service;1"]
+            .getService(Ci.nsIObserverService);
+
+
+var requestObserver = null;
+
+function AuthPrompt()
+{}
+
+AuthPrompt.prototype = {
+  user: "guest",
+  pass: "guest",
+
+  QueryInterface: function authprompt_qi(iid) {
+    if (iid.equals(Ci.nsISupports) ||
+        iid.equals(Ci.nsIAuthPrompt))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  prompt: function ap1_prompt(title, text, realm, save, defaultText, result) {
+    do_throw("unexpected prompt call");
+  },
+
+  promptUsernameAndPassword:
+    function promptUP(title, text, realm, savePW, user, pw)
+  {
+    user.value = this.user;
+    pw.value = this.pass;
+
+    obs.addObserver(requestObserver, "http-on-before-connect");
+    obs.addObserver(requestObserver, "http-on-modify-request");
+    return true;
+  },
+
+  promptPassword: function promptPW(title, text, realm, save, pwd) {
+    do_throw("unexpected promptPassword call");
+  }
+
+};
+
+function requestListenerObserver(suspendOnBeforeConnect, suspendOnModifyRequest)
+{
+  this.suspendOnModifyRequest = suspendOnModifyRequest;
+  this.suspendOnBeforeConnect = suspendOnBeforeConnect;
+}
+
+requestListenerObserver.prototype = {
+  suspendOnModifyRequest: false,
+  suspendOnBeforeConnect: false,
+  gotOnBeforeConnect: false,
+  resumeOnBeforeConnect: false,
+  gotOnModifyRequest: false,
+  resumeOnModifyRequest: false,
+  QueryInterface: function queryinterface(iid) {
+    if (iid.equals(Ci.nsISupports) ||
+        iid.equals(Ci.nsIObserver))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  observe: function(subject, topic, data) {
+    if (topic === "http-on-before-connect" &&
+        subject instanceof Ci.nsIHttpChannel) {
+      if (this.suspendOnBeforeConnect) {
+        var chan = subject.QueryInterface(Ci.nsIHttpChannel);
+        executeSoon(() => {
+          this.resumeOnBeforeConnect = true;
+          chan.resume();
+        });
+        this.gotOnBeforeConnect = true;
+        chan.suspend();
+      }
+    }
+    else if (topic === "http-on-modify-request" &&
+        subject instanceof Ci.nsIHttpChannel) {
+      if (this.suspendOnModifyRequest) {
+        var chan = subject.QueryInterface(Ci.nsIHttpChannel);
+        executeSoon(() => {
+          this.resumeOnModifyRequest = true;
+          chan.resume();
+        });
+        this.gotOnModifyRequest = true;
+        chan.suspend();
+      }
+    }
+  }
+};
+
+function Requestor() {};
+
+Requestor.prototype = {
+  QueryInterface: function requestor_qi(iid) {
+    if (iid.equals(Ci.nsISupports) ||
+        iid.equals(Ci.nsIInterfaceRequestor))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  getInterface: function requestor_gi(iid) {
+    if (iid.equals(Ci.nsIAuthPrompt)) {
+      // Allow the prompt to store state by caching it here
+      if (!this.prompt)
+        this.prompt = new AuthPrompt();
+      return this.prompt;
+    }
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  prompt: null
+};
+
+var listener = {
+  expectedCode: -1, // Uninitialized
+
+  onStartRequest: function test_onStartR(request, ctx) {
+    try {
+      if (!Components.isSuccessCode(request.status))
+        do_throw("Channel should have a success code!");
+
+      if (!(request instanceof Ci.nsIHttpChannel))
+        do_throw("Expecting an HTTP channel");
+
+      Assert.equal(request.responseStatus, this.expectedCode);
+      // The request should be succeeded iff we expect 200
+      Assert.equal(request.requestSucceeded, this.expectedCode == 200);
+
+    } catch (e) {
+      do_throw("Unexpected exception: " + e);
+    }
+    throw Cr.NS_ERROR_ABORT;
+  },
+
+  onDataAvailable: function test_ODA() {
+    do_throw("Should not get any data!");
+  },
+
+  onStopRequest: function test_onStopR(request, ctx, status) {
+    Assert.equal(status, Cr.NS_ERROR_ABORT);
+    if (requestObserver.suspendOnBeforeConnect) {
+      Assert.ok(requestObserver.gotOnBeforeConnect && requestObserver.resumeOnBeforeConnect);
+    }
+    if (requestObserver.suspendOnModifyRequest) {
+      Assert.ok(requestObserver.gotOnModifyRequest && requestObserver.resumeOnModifyRequest);
+    }
+    obs.removeObserver(requestObserver, "http-on-before-connect");
+    obs.removeObserver(requestObserver, "http-on-modify-request");
+    moveToNextTest();
+  }
+};
+
+function makeChan(url, loadingUrl) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].
+              getService(Ci.nsIIOService);
+  var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+              .getService(Ci.nsIScriptSecurityManager);
+  var principal = ssm.createCodebasePrincipal(ios.newURI(loadingUrl), {});
+  return NetUtil.newChannel(
+    { uri: url, loadingPrincipal: principal,
+      securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+      contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+    });
+}
+
+var tests = [test_suspend_on_before_connect,
+             test_suspend_on_modify_request,
+             test_suspend_all];
+
+var current_test = 0;
+
+var httpserv = null;
+
+function moveToNextTest() {
+  if (current_test < (tests.length - 1)) {
+    // First, gotta clear the auth cache
+    Cc["@mozilla.org/network/http-auth-manager;1"]
+      .getService(Ci.nsIHttpAuthManager)
+      .clearAll();
+
+    current_test++;
+    tests[current_test]();
+  } else {
+    do_test_pending();
+    httpserv.stop(do_test_finished);
+  }
+
+  do_test_finished();
+}
+
+function run_test() {
+  httpserv = new HttpServer();
+
+  httpserv.registerPathHandler("/auth", authHandler);
+
+  httpserv.start(-1);
+
+  tests[0]();
+}
+
+function test_suspend_on_auth(suspendOnBeforeConnect, suspendOnModifyRequest) {
+  var chan = makeChan(URL + "/auth", URL);
+  requestObserver =
+    new requestListenerObserver(suspendOnBeforeConnect, suspendOnModifyRequest);
+  chan.notificationCallbacks = new Requestor();
+  listener.expectedCode = 200; // OK
+  chan.asyncOpen2(listener);
+
+  do_test_pending();
+}
+
+function test_suspend_on_before_connect()
+{
+  test_suspend_on_auth(true, false);
+}
+
+function test_suspend_on_modify_request()
+{
+  test_suspend_on_auth(false, true);
+}
+
+function test_suspend_all()
+{
+  test_suspend_on_auth(true, true);
+}
+
+
+// PATH HANDLERS
+
+// /auth
+function authHandler(metadata, response) {
+  // btoa("guest:guest"), but that function is not available here
+  var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+  var body;
+  if (metadata.hasHeader("Authorization") &&
+      metadata.getHeader("Authorization") == expectedHeader)
+  {
+    response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+    body = "success";
+  }
+  else
+  {
+    // didn't know guest:guest, failure
+    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+    body = "failed";
+  }
+
+  response.bodyOutputStream.write(body, body.length);
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_examine_merged_response.js
@@ -0,0 +1,193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file tests async handling of a channel suspended in
+// notifying http-on-examine-merged-response observers.
+// Note that this test is developed based on test_bug482601.js.
+
+
+ChromeUtils.import("resource://testing-common/httpd.js");
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserv = null;
+var test_nr = 0;
+var buffer = "";
+var observerCalled = false;
+var channelResumed = false;
+
+var observer = {
+  QueryInterface: function (aIID) {
+    if (aIID.equals(Ci.nsISupports) ||
+        aIID.equals(Ci.nsIObserver))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  observe: function(subject, topic, data) {
+    if (topic === "http-on-examine-merged-response" &&
+        subject instanceof Ci.nsIHttpChannel) {
+      var chan = subject.QueryInterface(Ci.nsIHttpChannel);
+      executeSoon(() => {
+        Assert.equal(channelResumed,false);
+        channelResumed = true;
+        chan.resume();
+      });
+      Assert.equal(observerCalled,false);
+      observerCalled = true;
+      chan.suspend();
+    }
+  }
+};
+
+var listener = {
+  onStartRequest: function (request, ctx) {
+    buffer = "";
+  },
+
+  onDataAvailable: function (request, ctx, stream, offset, count) {
+    buffer = buffer.concat(read_stream(stream, count));
+  },
+
+  onStopRequest: function (request, ctx, status) {
+    Assert.equal(status, Cr.NS_OK);
+    Assert.equal(buffer, "0123456789");
+    Assert.equal(channelResumed, true);
+    Assert.equal(observerCalled, true);
+    test_nr++;
+    do_timeout(0, do_test);
+  }
+};
+
+function run_test() {
+  httpserv = new HttpServer();
+  httpserv.registerPathHandler("/path/partial", path_partial);
+  httpserv.registerPathHandler("/path/cached", path_cached);
+  httpserv.start(-1);
+
+  var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+  obs.addObserver(observer, "http-on-examine-merged-response");
+
+  do_timeout(0, do_test);
+  do_test_pending();
+}
+
+function do_test() {
+  if (test_nr < tests.length) {
+    tests[test_nr]();
+  }
+  else {
+    var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+    obs.removeObserver(observer, "http-on-examine-merged-response");
+    httpserv.stop(do_test_finished);
+  }
+}
+
+var tests = [test_partial,
+             test_cached];
+
+function makeChan(url) {
+  return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+                .QueryInterface(Ci.nsIHttpChannel);
+}
+
+function storeCache(aCacheEntry, aResponseHeads, aContent) {
+  aCacheEntry.setMetaDataElement("request-method", "GET");
+  aCacheEntry.setMetaDataElement("response-head", aResponseHeads);
+  aCacheEntry.setMetaDataElement("charset", "ISO-8859-1");
+
+  var oStream = aCacheEntry.openOutputStream(0, aContent.length);
+  var written = oStream.write(aContent, aContent.length);
+  if (written != aContent.length) {
+    do_throw("oStream.write has not written all data!\n" +
+             "  Expected: " + written  + "\n" +
+             "  Actual: " + aContent.length + "\n");
+  }
+  oStream.close();
+  aCacheEntry.close();
+}
+
+function test_partial() {
+  observerCalled = false;
+  channelResumed = false;
+  asyncOpenCacheEntry("http://localhost:" + httpserv.identity.primaryPort +
+                      "/path/partial",
+                      "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+                      test_partial2);
+}
+
+function test_partial2(status, entry) {
+  Assert.equal(status, Cr.NS_OK);
+  storeCache(entry,
+             "HTTP/1.1 200 OK\r\n" +
+             "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+             "Server: httpd.js\r\n" +
+             "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+             "Accept-Ranges: bytes\r\n" +
+             "Content-Length: 10\r\n" +
+             "Content-Type: text/plain\r\n",
+             "0123");
+
+  observers_called = "";
+
+  var chan = makeChan("http://localhost:" + httpserv.identity.primaryPort +
+                      "/path/partial");
+  chan.asyncOpen2(listener);
+}
+
+function test_cached() {
+  observerCalled = false;
+  channelResumed = false;
+  asyncOpenCacheEntry("http://localhost:" + httpserv.identity.primaryPort +
+                      "/path/cached",
+                      "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+                      test_cached2);
+}
+
+function test_cached2(status, entry) {
+  Assert.equal(status, Cr.NS_OK);
+  storeCache(entry,
+             "HTTP/1.1 200 OK\r\n" +
+             "Date: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+             "Server: httpd.js\r\n" +
+             "Last-Modified: Thu, 1 Jan 2009 00:00:00 GMT\r\n" +
+             "Accept-Ranges: bytes\r\n" +
+             "Content-Length: 10\r\n" +
+             "Content-Type: text/plain\r\n",
+             "0123456789");
+
+  observers_called = "";
+
+  var chan = makeChan("http://localhost:" + httpserv.identity.primaryPort +
+                      "/path/cached");
+  chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+  chan.asyncOpen2(listener);
+}
+
+// PATHS
+
+// /path/partial
+function path_partial(metadata, response) {
+  Assert.ok(metadata.hasHeader("If-Range"));
+  Assert.equal(metadata.getHeader("If-Range"),
+               "Thu, 1 Jan 2009 00:00:00 GMT");
+  Assert.ok(metadata.hasHeader("Range"));
+  Assert.equal(metadata.getHeader("Range"), "bytes=4-");
+
+  response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+  response.setHeader("Content-Range", "bytes 4-9/10", false);
+  response.setHeader("Content-Type", "text/plain", false);
+  response.setHeader("Last-Modified", "Thu, 1 Jan 2009 00:00:00 GMT");
+
+  var body = "456789";
+  response.bodyOutputStream.write(body, body.length);
+}
+
+// /path/cached
+function path_cached(metadata, response) {
+  Assert.ok(metadata.hasHeader("If-Modified-Since"));
+  Assert.equal(metadata.getHeader("If-Modified-Since"),
+               "Thu, 1 Jan 2009 00:00:00 GMT");
+
+  response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -420,8 +420,10 @@ skip-if = os == "android"
 [test_captive_portal_service.js]
 skip-if = os == "android" # CP service is disabled on Android
 run-sequentially = node server exceptions dont replay well
 [test_esni_dns_fetch.js]
 # http2-using tests require node available
 skip-if = os == "android"
 [test_network_connectivity_service.js]
 skip-if = os == "android" # DNSv6 issues on android
+[test_suspend_channel_on_authRetry.js]
+[test_suspend_channel_on_examine_merged_response.js]
--- a/services/settings/RemoteSettingsClient.jsm
+++ b/services/settings/RemoteSettingsClient.jsm
@@ -328,99 +328,51 @@ class RemoteSettingsClient extends Event
         const { ok } = syncResult;
         if (!ok) {
           // Some synchronization conflicts occured.
           reportStatus = UptakeTelemetry.STATUS.CONFLICT_ERROR;
           throw new Error("Sync failed");
         }
       } catch (e) {
         if (e.message.includes(INVALID_SIGNATURE)) {
-          // Signature verification failed during synchronzation.
+          // Signature verification failed during synchronization.
           reportStatus = UptakeTelemetry.STATUS.SIGNATURE_ERROR;
-          // if sync fails with a signature error, it's likely that our
+          // If sync fails with a signature error, it's likely that our
           // local data has been modified in some way.
           // We will attempt to fix this by retrieving the whole
           // remote collection.
-          const payload = await fetchRemoteRecords(collection.bucket, collection.name, expectedTimestamp);
           try {
-            await this._validateCollectionSignature(payload.data,
-                                                    payload.last_modified,
-                                                    collection,
-                                                    { expectedTimestamp, ignoreLocal: true });
+            syncResult = await this._retrySyncFromScratch(collection, expectedTimestamp);
           } catch (e) {
+            // If the signature fails again, or if an error occured during wiping out the
+            // local data, then we report it as a *signature retry* error.
             reportStatus = UptakeTelemetry.STATUS.SIGNATURE_RETRY_ERROR;
             throw e;
           }
-
-          // The signature is good (we haven't thrown).
-          // Now we will Inspect what we had locally.
-          const { data: oldData } = await collection.list({ order: "" }); // no need to sort.
-
-          // We build a sync result as if a diff-based sync was performed.
-          syncResult = { created: [], updated: [], deleted: [] };
-
-          // If the remote last_modified is newer than the local last_modified,
-          // replace the local data
-          const localLastModified = await collection.db.getLastModified();
-          if (payload.last_modified >= localLastModified) {
-            const { data: newData } = payload;
-            await collection.clear();
-            await collection.loadDump(newData);
-
-            // Compare local and remote to populate the sync result
-            const oldById = new Map(oldData.map(e => [e.id, e]));
-            for (const r of newData) {
-              const old = oldById.get(r.id);
-              if (old) {
-                if (old.last_modified != r.last_modified) {
-                  syncResult.updated.push({ old, new: r });
-                }
-                oldById.delete(r.id);
-              } else {
-                syncResult.created.push(r);
-              }
-            }
-            // Records that remain in our map now are those missing from remote
-            syncResult.deleted = Array.from(oldById.values());
-          }
-
         } else {
           // The sync has thrown, it can be related to metadata, network or a general error.
           if (e.message == MISSING_SIGNATURE) {
             // Collection metadata has no signature info, no need to retry.
             reportStatus = UptakeTelemetry.STATUS.SIGNATURE_ERROR;
           } else if (/NetworkError/.test(e.message)) {
             reportStatus = UptakeTelemetry.STATUS.NETWORK_ERROR;
           } else if (/Backoff/.test(e.message)) {
             reportStatus = UptakeTelemetry.STATUS.BACKOFF;
           } else {
             reportStatus = UptakeTelemetry.STATUS.SYNC_ERROR;
           }
           throw e;
         }
       }
 
-      // Handle the obtained records (ie. apply locally through events).
-      // Build the event data list. It should be filtered (ie. by application target)
-      const { created: allCreated, updated: allUpdated, deleted: allDeleted } = syncResult;
-      const [created, deleted, updatedFiltered] = await Promise.all(
-          [allCreated, allDeleted, allUpdated.map(e => e.new)].map(this._filterEntries.bind(this))
-        );
-      // For updates, keep entries whose updated form matches the target.
-      const updatedFilteredIds = new Set(updatedFiltered.map(e => e.id));
-      const updated = allUpdated.filter(({ new: { id } }) => updatedFilteredIds.has(id));
-
+      const filteredSyncResult = await this._filterSyncResult(collection, syncResult);
       // If every changed entry is filtered, we don't even fire the event.
-      if (created.length || updated.length || deleted.length) {
-        // Read local collection of records (also filtered).
-        const { data: allData } = await collection.list({ order: "" }); // no need to sort.
-        const current = await this._filterEntries(allData);
-        const payload = { data: { current, created, updated, deleted } };
+      if (filteredSyncResult) {
         try {
-          await this.emit("sync", payload);
+          await this.emit("sync", { data: filteredSyncResult });
         } catch (e) {
           reportStatus = UptakeTelemetry.STATUS.APPLY_ERROR;
           throw e;
         }
       }
 
     } catch (e) {
       // No specific error was tracked, mark it as unknown.
@@ -439,17 +391,17 @@ class RemoteSettingsClient extends Event
   }
 
   /**
    * Fetch the signature info from the collection metadata and verifies that the
    * local set of records has the same.
    *
    * @param {Array<Object>} remoteRecords   The list of changes to apply to the local database.
    * @param {int} timestamp                 The timestamp associated with the list of remote records.
-   * @param {Collection} collection         Kinto.js Collection instance.
+   * @param {Collection} kintoCollection    Kinto.js Collection instance.
    * @param {Object} options
    * @param {int} options.expectedTimestamp Cache busting of collection metadata
    * @param {Boolean} options.ignoreLocal   When the signature verification is retried, since we refetch
    *                                        the whole collection, we don't take into account the local
    *                                        data (default: `false`)
    * @returns {Promise}
    */
   async _validateCollectionSignature(remoteRecords, timestamp, kintoCollection, options = {}) {
@@ -474,16 +426,100 @@ class RemoteSettingsClient extends Event
                                          "p384ecdsa=" + signature,
                                          certChain,
                                          this.signerName)) {
       throw new Error(INVALID_SIGNATURE + ` (${bucket}/${collection})`);
     }
   }
 
   /**
+   * Fetch the whole list of records from the server, verify the signature again
+   * and then compute a synchronization result as if the diff-based sync happened.
+   * And eventually, wipe out the local data.
+   *
+   * @param {Collection} kintoCollection    Kinto.js Collection instance.
+   * @param {int}        expectedTimestamp  Cache busting of collection metadata
+   *
+   * @returns {Promise<Object>} the computed sync result.
+   */
+  async _retrySyncFromScratch(kintoCollection, expectedTimestamp) {
+    const payload = await fetchRemoteRecords(kintoCollection.bucket, kintoCollection.name, expectedTimestamp);
+    await this._validateCollectionSignature(payload.data,
+      payload.last_modified,
+      kintoCollection,
+      { expectedTimestamp, ignoreLocal: true });
+
+    // The signature is good (we haven't thrown).
+    // Now we will Inspect what we had locally.
+    const { data: oldData } = await kintoCollection.list({ order: "" }); // no need to sort.
+
+    // We build a sync result as if a diff-based sync was performed.
+    const syncResult = { created: [], updated: [], deleted: [] };
+
+    // If the remote last_modified is newer than the local last_modified,
+    // replace the local data
+    const localLastModified = await kintoCollection.db.getLastModified();
+    if (payload.last_modified >= localLastModified) {
+      const { data: newData } = payload;
+      await kintoCollection.clear();
+      await kintoCollection.loadDump(newData);
+
+      // Compare local and remote to populate the sync result
+      const oldById = new Map(oldData.map(e => [e.id, e]));
+      for (const r of newData) {
+        const old = oldById.get(r.id);
+        if (old) {
+          if (old.last_modified != r.last_modified) {
+            syncResult.updated.push({ old, new: r });
+          }
+          oldById.delete(r.id);
+        } else {
+          syncResult.created.push(r);
+        }
+      }
+      // Records that remain in our map now are those missing from remote
+      syncResult.deleted = Array.from(oldById.values());
+    }
+    return syncResult;
+  }
+
+  /**
+   * Use the filter func to filter the lists of changes obtained from synchronization,
+   * and return them along with the filtered list of local records.
+   *
+   * If the filtered lists of changes are all empty, we return null (and thus don't
+   * bother listing local DB).
+   *
+   * @param {Collection} kintoCollection  Kinto.js Collection instance.
+   * @param {Object}     syncResult       Synchronization result without filtering.
+   *
+   * @returns {Promise<Object>} the filtered list of local records, plus the filtered
+   *                            list of created, updated and deleted records.
+   */
+  async _filterSyncResult(kintoCollection, syncResult) {
+    // Handle the obtained records (ie. apply locally through events).
+    // Build the event data list. It should be filtered (ie. by application target)
+    const { created: allCreated, updated: allUpdated, deleted: allDeleted } = syncResult;
+    const [created, deleted, updatedFiltered] = await Promise.all(
+      [allCreated, allDeleted, allUpdated.map(e => e.new)].map(this._filterEntries.bind(this))
+    );
+    // For updates, keep entries whose updated form matches the target.
+    const updatedFilteredIds = new Set(updatedFiltered.map(e => e.id));
+    const updated = allUpdated.filter(({ new: { id } }) => updatedFilteredIds.has(id));
+
+    if (!created.length && !updated.length && !deleted.length) {
+      return null;
+    }
+    // Read local collection of records (also filtered).
+    const { data: allData } = await kintoCollection.list({ order: "" }); // no need to sort.
+    const current = await this._filterEntries(allData);
+    return { created, updated, deleted, current };
+  }
+
+  /**
    * Filter entries for which calls to `this.filterFunc` returns null.
    *
    * @param {Array<Objet>} data
    * @returns {Array<Object>}
    */
   async _filterEntries(data) {
     if (!this.filterFunc) {
       return data;
--- a/taskcluster/ci/test/marionette.yml
+++ b/taskcluster/ci/test/marionette.yml
@@ -25,20 +25,16 @@ marionette:
     max-run-time:
         by-test-platform:
             android-em.*: 3600
             default: 5400
     instance-size:
         by-test-platform:
             android-em.*: xlarge
             default: default
-    tier:
-        by-test-platform:
-            android-em.*: 2
-            default: default
     chunks:
         by-test-platform:
             android-em.*: 10
             default: 1
 
 marionette-headless:
     description: "Marionette headless unittest run"
     treeherder-symbol: MnH
--- a/testing/geckodriver/README.md
+++ b/testing/geckodriver/README.md
@@ -67,12 +67,12 @@ See our [contribution documentation] for more information.
 
 Contact
 -------
 
 The mailing list for geckodriver discussion is
 tools-marionette@lists.mozilla.org ([subscribe], [archive]).
 
 There is also an IRC channel to talk about using and developing
-geckodriver in #ateam on irc.mozilla.org.
+geckodriver in #interop on irc.mozilla.org.
 
 [subscribe]: https://lists.mozilla.org/listinfo/tools-marionette
 [archive]: https://lists.mozilla.org/pipermail/tools-marionette/
--- a/testing/marionette/doc/NewContributors.md
+++ b/testing/marionette/doc/NewContributors.md
@@ -19,17 +19,17 @@ Accounts, communication
   1. Set up a [Bugzilla] account (and, if you like, a [Mozillians] profile).
      Please include your IRC nickname in both of these accounts so we can work
      with you more easily. For example, Eve Smith would set the Bugzilla name
      to "Eve Smith (:esmith)", where "esmith" is the IRC nick.
 
   2. For a direct communication with us it will be beneficial to setup [IRC].
      Make sure to also register your nickname as described in the linked document.
 
-  3. Join our #ateam channel, and introduce yourself to the team. :ato,
+  3. Join our #interop channel, and introduce yourself to the team. :ato,
      :AutomatedTester, :maja_zf, and :whimboo are all familiar with Marionette.
      We're nice, I promise, but we might not answer right away due to different
      time zones, time off, etc. So please be patient.
 
   4. When you want to ask a question on IRC, just go ahead an ask it even if
      no one appears to be around/responding.
      Provide lots of detail so that we have a better chance of helping you.
      If you don't get an answer right away, check again in a few hours --
--- a/testing/marionette/doc/index.rst
+++ b/testing/marionette/doc/index.rst
@@ -57,14 +57,14 @@ Bugs are tracked in the `Testing :: Mari
 
 
 Communication
 =============
 
 The mailing list for Marionette discussion is
 tools-marionette@lists.mozilla.org (`subscribe`_, `archive`_).
 
-If you prefer real-time chat, there is often someone in the #ateam IRC
+If you prefer real-time chat, there is often someone in the #interop IRC
 channel on irc.mozilla.org.  Don’t ask if you may ask a question; just go ahead
 and ask, and please wait for an answer as we might not be in your timezone.
 
 .. _subscribe: https://lists.mozilla.org/listinfo/tools-marionette
 .. _archive: https://lists.mozilla.org/pipermail/tools-marionette/
--- a/testing/tps/create_venv.py
+++ b/testing/tps/create_venv.py
@@ -144,17 +144,17 @@ def main():
                       dest='username',
                       metavar='FX_ACCOUNT_USERNAME',
                       default=None,
                       help='The Firefox Account username.')
 
     (options, args) = parser.parse_args(args=None, values=None)
 
     if len(args) != 1:
-         parser.error('Path to the environment has to be specified')
+        parser.error('Path to the environment has to be specified')
     target = args[0]
     assert(target)
 
     setup_virtualenv(target, python_bin=options.python)
 
     # Activate tps environment
     tps_env = os.path.join(target, activate_env)
     execfile(tps_env, dict(__file__=tps_env))
@@ -173,27 +173,28 @@ def main():
     else:
         testdir = os.path.join(here, 'tests')
         extdir = os.path.join(here, 'extensions')
 
     if not options.keep_config:
         update_configfile(os.path.join(here, 'config', 'config.json.in'),
                           os.path.join(target, 'config.json'),
                           replacements={
-                          '__TESTDIR__': testdir.replace('\\','/'),
-                          '__EXTENSIONDIR__': extdir.replace('\\','/'),
+                          '__TESTDIR__': testdir.replace('\\', '/'),
+                          '__EXTENSIONDIR__': extdir.replace('\\', '/'),
                           '__FX_ACCOUNT_USERNAME__': options.username,
                           '__FX_ACCOUNT_PASSWORD__': options.password,
                           '__SYNC_ACCOUNT_USERNAME__': options.sync_username,
                           '__SYNC_ACCOUNT_PASSWORD__': options.sync_password,
                           '__SYNC_ACCOUNT_PASSPHRASE__': options.sync_passphrase})
 
         if not (options.username and options.password):
             print '\nFirefox Account credentials not specified.'
         if not (options.sync_username and options.sync_password and options.passphrase):
             print '\nFirefox Sync account credentials not specified.'
 
     # Print the user instructions
     print usage_message.format(TARGET=target,
                                BIN_NAME=bin_name)
 
+
 if __name__ == "__main__":
     main()
--- a/testing/tps/setup.py
+++ b/testing/tps/setup.py
@@ -12,30 +12,30 @@ deps = ['httplib2 == 0.9.2',
         'mozfile >= 1.2',
         'mozhttpd == 0.7',
         'mozinfo >= 0.10',
         'mozinstall == 1.16',
         'mozprocess == 0.26',
         'mozprofile ~= 2.1',
         'mozrunner ~= 7.2',
         'mozversion == 1.5',
-       ]
+        ]
 
 # we only support python 2.6+ right now
 assert sys.version_info[0] == 2
 assert sys.version_info[1] >= 6
 
 setup(name='tps',
       version=version,
       description='run automated multi-profile sync tests',
       long_description="""\
 """,
       classifiers=['Programming Language :: Python :: 2.7',
-		   'Programming Language :: Python :: 2 :: Only',
-		  ], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+                   'Programming Language :: Python :: 2 :: Only',
+                   ],  # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
       keywords='',
       author='Mozilla Automation and Tools team',
       author_email='tools@lists.mozilla.org',
       url='https://developer.mozilla.org/en-US/docs/TPS',
       license='MPL 2.0',
       packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
       include_package_data=True,
       zip_safe=False,
--- a/testing/tps/tps/__init__.py
+++ b/testing/tps/tps/__init__.py
@@ -1,6 +1,7 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+# flake8: noqa
 from . firefoxrunner import TPSFirefoxRunner
 from . testrunner import TPSTestRunner
--- a/testing/tps/tps/cli.py
+++ b/testing/tps/tps/cli.py
@@ -29,21 +29,21 @@ def main():
                       default=None,
                       help='path to the config file to use default: %default]')
     parser.add_option('--debug',
                       action='store_true',
                       dest='debug',
                       default=False,
                       help='run in debug mode')
     parser.add_option('--ignore-unused-engines',
-                       default=False,
-                       action='store_true',
-                       dest='ignore_unused_engines',
-                       help='If defined, do not load unused engines in individual tests.'
-                            ' Has no effect for pulse monitor.')
+                      default=False,
+                      action='store_true',
+                      dest='ignore_unused_engines',
+                      help='If defined, do not load unused engines in individual tests.'
+                      ' Has no effect for pulse monitor.')
     parser.add_option('--logfile',
                       action='store',
                       type='string',
                       dest='logfile',
                       default='tps.log',
                       help='path to the log file [default: %default]')
     parser.add_option('--mobile',
                       action='store_true',
@@ -117,16 +117,17 @@ def main():
                         debug=options.debug,
                         ignore_unused_engines=options.ignore_unused_engines,
                         logfile=options.logfile,
                         mobile=options.mobile,
                         resultfile=options.resultfile,
                         rlock=rlock,
                         testfile=testfile,
                         stop_on_error=options.stop_on_error,
-                      )
+                        )
     TPS.run_tests()
 
     if TPS.numfailed > 0 or TPS.numpassed == 0:
         sys.exit(1)
 
+
 if __name__ == '__main__':
     main()
--- a/testing/tps/tps/firefoxrunner.py
+++ b/testing/tps/tps/firefoxrunner.py
@@ -1,14 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
-import copy
 import httplib2
 import os
 
 import mozfile
 import mozinstall
 from mozprofile import Profile
 from mozrunner import FirefoxRunner
 
@@ -29,17 +28,17 @@ class TPSFirefoxRunner(object):
 
     def __del__(self):
         if self.installdir:
             mozfile.remove(self.installdir, True)
 
     def download_url(self, url, dest=None):
         h = httplib2.Http()
         resp, content = h.request(url, 'GET')
-        if dest == None:
+        if dest is None:
             dest = os.path.basename(url)
 
         local = open(dest, 'wb')
         local.write(content)
         local.close()
         return dest
 
     def download_build(self, installdir='downloadedbuild', appname='firefox'):
--- a/testing/tps/tps/phase.py
+++ b/testing/tps/tps/phase.py
@@ -1,25 +1,26 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import re
 import os.path
 
+
 class TPSTestPhase(object):
 
     lineRe = re.compile(
         r'^(.*?)test phase (?P<matchphase>[^\s]+): (?P<matchstatus>.*)$')
 
     def __init__(self, phase, profile, testname, testpath, logfile, env,
                  firefoxRunner, logfn, ignore_unused_engines=False):
         self.phase = phase
         self.profile = profile
-        self.testname = str(testname) # this might be passed in as unicode
+        self.testname = str(testname)  # this might be passed in as unicode
         self.testpath = testpath
         self.logfile = logfile
         self.env = env
         self.firefoxRunner = firefoxRunner
         self.log = logfn
         self.ignore_unused_engines = ignore_unused_engines
         self._status = None
         self.errline = ''
@@ -33,17 +34,17 @@ class TPSTestPhase(object):
 
         prefs = {
             "testing.tps.testFile": os.path.abspath(self.testpath),
             "testing.tps.testPhase": self.phase,
             "testing.tps.logFile": self.logfile,
             "testing.tps.ignoreUnusedEngines": self.ignore_unused_engines
         }
 
-        self.profile.set_preferences(prefs);
+        self.profile.set_preferences(prefs)
 
         self.log('\nLaunching Firefox for phase %s with prefs %s\n' %
                  (self.phase, str(prefs)))
 
         self.firefoxRunner.run(env=self.env,
                                args=[],
                                profile=self.profile)
 
--- a/testing/tps/tps/testrunner.py
+++ b/testing/tps/tps/testrunner.py
@@ -1,16 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import json
 import os
-import platform
-import random
 import re
 import tempfile
 import time
 import traceback
 
 from mozhttpd import MozHttpd
 import mozinfo
 from mozprofile import Profile
@@ -183,68 +181,71 @@ class TPSTestRunner(object):
         f.close()
 
     def _zip_add_file(self, zip, file, rootDir):
         zip.write(os.path.join(rootDir, file), file)
 
     def _zip_add_dir(self, zip, dir, rootDir):
         try:
             zip.write(os.path.join(rootDir, dir), dir)
-        except:
+        except Exception:
             # on some OS's, adding directory entries doesn't seem to work
             pass
         for root, dirs, files in os.walk(os.path.join(rootDir, dir)):
             for f in files:
                 zip.write(os.path.join(root, f), os.path.join(dir, f))
 
     def handle_phase_failure(self, profiles):
         for profile in profiles:
-            self.log('\nDumping sync log for profile %s\n' %  profiles[profile].profile)
-            for root, dirs, files in os.walk(os.path.join(profiles[profile].profile, 'weave', 'logs')):
+            self.log('\nDumping sync log for profile %s\n'
+                     % profiles[profile].profile)
+            for root, dirs, files in os.walk(
+                    os.path.join(profiles[profile].profile, 'weave', 'logs')):
                 for f in files:
-                    weavelog = os.path.join(profiles[profile].profile, 'weave', 'logs', f)
+                    weavelog = os.path.join(
+                        profiles[profile].profile, 'weave', 'logs', f)
                     if os.access(weavelog, os.F_OK):
                         with open(weavelog, 'r') as fh:
                             for line in fh:
                                 possible_time = line[0:13]
                                 if len(possible_time) == 13 and possible_time.isdigit():
                                     time_ms = int(possible_time)
                                     formatted = time.strftime('%Y-%m-%d %H:%M:%S',
-                                            time.localtime(time_ms / 1000))
+                                                              time.localtime(time_ms / 1000))
                                     self.log('%s.%03d %s' % (
-                                        formatted, time_ms % 1000, line[14:] ))
+                                        formatted, time_ms % 1000, line[14:]))
                                 else:
                                     self.log(line)
 
     def run_single_test(self, testdir, testname):
         testpath = os.path.join(testdir, testname)
         self.log("Running test %s\n" % testname, True)
 
         # Read and parse the test file, merge it with the contents of the config
         # file, and write the combined output to a temporary file.
         f = open(testpath, 'r')
         testcontent = f.read()
         f.close()
         try:
             test = json.loads(testcontent)
-        except:
+        except Exception:
             test = json.loads(testcontent[testcontent.find('{'):testcontent.find('}') + 1])
 
         self.preferences['tps.seconds_since_epoch'] = int(time.time())
 
         # generate the profiles defined in the test, and a list of test phases
         profiles = {}
         phaselist = []
         for phase in test:
             profilename = test[phase]
 
             # create the profile if necessary
-            if not profilename in profiles:
-                profiles[profilename] = Profile(preferences = self.preferences.copy(),
-                                                addons = self.extensions)
+            if profilename not in profiles:
+                profiles[profilename] = Profile(preferences=self.preferences.copy(),
+                                                addons=self.extensions)
 
             # create the test phase
             phaselist.append(TPSTestPhase(
                 phase,
                 profiles[profilename],
                 testname,
                 testpath,
                 self.logfile,
@@ -257,17 +258,17 @@ class TPSTestRunner(object):
         phaselist = sorted(phaselist, key=lambda phase: phase.phase)
 
         # run each phase in sequence, aborting at the first failure
         failed = False
         for phase in phaselist:
             phase.run()
             if phase.status != 'PASS':
                 failed = True
-                break;
+                break
 
         for profilename in profiles:
             print "### Cleanup Profile ", profilename
             cleanup_phase = TPSTestPhase(
                 'cleanup-' + profilename,
                 profiles[profilename], testname,
                 testpath,
                 self.logfile,
@@ -298,60 +299,61 @@ class TPSTestRunner(object):
         else:
             # we only care about the log data for this specific test
             logdata = logdata[logdata.find('Running test %s' % (str(testname))):]
 
         result = {
           'PASS': lambda x: ('TEST-PASS', ''),
           'FAIL': lambda x: ('TEST-UNEXPECTED-FAIL', x.rstrip()),
           'unknown': lambda x: ('TEST-UNEXPECTED-FAIL', 'test did not complete')
-        } [phase.status](phase.errline)
-        logstr = "\n%s | %s%s\n" % (result[0], testname, (' | %s' % result[1] if result[1] else ''))
+        }[phase.status](phase.errline)
+        logstr = "\n%s | %s%s\n" % (result[0], testname, (' | %s' %
+                                                          result[1] if result[1] else ''))
 
         try:
             repoinfo = mozversion.get_version(self.binary)
-        except:
+        except Exception:
             repoinfo = {}
         apprepo = repoinfo.get('application_repository', '')
         appchangeset = repoinfo.get('application_changeset', '')
 
         # save logdata to a temporary file for posting to the db
         tmplogfile = None
         if logdata:
             tmplogfile = TempFile(prefix='tps_log_')
             tmplogfile.write(logdata)
             tmplogfile.close()
             self.errorlogs[testname] = tmplogfile
 
-        resultdata = ({ 'productversion': { 'version': firefox_version,
-                                            'buildid': firefox_buildid,
-                                            'builddate': firefox_buildid[0:8],
-                                            'product': 'Firefox',
-                                            'repository': apprepo,
-                                            'changeset': appchangeset,
+        resultdata = ({'productversion': {'version': firefox_version,
+                                          'buildid': firefox_buildid,
+                                          'builddate': firefox_buildid[0:8],
+                                          'product': 'Firefox',
+                                          'repository': apprepo,
+                                          'changeset': appchangeset,
                                           },
-                        'addonversion': { 'version': sync_version,
-                                          'product': 'Firefox Sync' },
-                        'name': testname,
-                        'message': result[1],
-                        'state': result[0],
-                        'logdata': logdata
-                      })
+                       'addonversion': {'version': sync_version,
+                                        'product': 'Firefox Sync'},
+                       'name': testname,
+                       'message': result[1],
+                       'state': result[0],
+                       'logdata': logdata
+                       })
 
         self.log(logstr, True)
         for phase in phaselist:
             print "\t%s: %s" % (phase.phase, phase.status)
 
         return resultdata
 
     def update_preferences(self):
         self.preferences = self.default_preferences.copy()
 
         if self.mobile:
-            self.preferences.update({'services.sync.client.type' : 'mobile'})
+            self.preferences.update({'services.sync.client.type': 'mobile'})
 
         # If we are using legacy Sync, then set a dummy username to force the
         # correct authentication type. Without this pref set to a value
         # without an '@' character, Sync will initialize for FxA.
         if self.config.get('auth_type', 'fx_account') != "fx_account":
             self.preferences.update({'services.sync.username': "dummy"})
 
         if self.debug:
@@ -383,40 +385,40 @@ class TPSTestRunner(object):
             # Create the Firefox runner, which will download and install the
             # build, as needed.
             if not self.firefoxRunner:
                 self.firefoxRunner = TPSFirefoxRunner(self.binary)
 
             # now, run the test group
             self.run_test_group()
 
-        except:
+        except Exception:
             traceback.print_exc()
             self.numpassed = 0
             self.numfailed = 1
             try:
                 self.writeToResultFile(self.postdata,
                                        '<pre>%s</pre>' % traceback.format_exc())
-            except:
+            except Exception:
                 traceback.print_exc()
         else:
             try:
 
                 if self.numfailed > 0 or self.numpassed == 0:
                     To = self.config['email'].get('notificationlist')
                 else:
                     To = self.config['email'].get('passednotificationlist')
                 self.writeToResultFile(self.postdata,
                                        sendTo=To)
-            except:
+            except Exception:
                 traceback.print_exc()
                 try:
                     self.writeToResultFile(self.postdata,
                                            '<pre>%s</pre>' % traceback.format_exc())
-                except:
+                except Exception:
                     traceback.print_exc()
 
         # release our lock
         if self.rlock:
             self.rlock.release()
 
         # dump out a summary of test results
         print 'Test Summary\n'
@@ -467,21 +469,22 @@ class TPSTestRunner(object):
                                  'name': result['name'],
                                  'message': result['message'],
                                  'logdata': result['logdata']})
             if result['state'] == 'TEST-PASS':
                 self.numpassed += 1
             else:
                 self.numfailed += 1
                 if self.stop_on_error:
-                    print '\nTest failed with --stop-on-error specified; not running any more tests.\n'
+                    print '\nTest failed with --stop-on-error specified; ' \
+                          'not running any more tests.\n'
                     break
 
         self.mozhttpd.stop()
 
         # generate the postdata we'll use to post the results to the db
-        self.postdata = { 'tests': self.results,
-                          'os': '%s %sbit' % (mozinfo.version, mozinfo.bits),
-                          'testtype': 'crossweave',
-                          'productversion': self.productversion,
-                          'addonversion': self.addonversion,
-                          'synctype': self.synctype,
-                        }
+        self.postdata = {'tests': self.results,
+                         'os': '%s %sbit' % (mozinfo.version, mozinfo.bits),
+                         'testtype': 'crossweave',
+                         'productversion': self.productversion,
+                         'addonversion': self.addonversion,
+                         'synctype': self.synctype,
+                         }
--- a/testing/webdriver/README.md
+++ b/testing/webdriver/README.md
@@ -36,12 +36,12 @@ To run the tests:
 
 Contact
 =======
 
 The mailing list for webdriver discussion is
 tools-marionette@lists.mozilla.org ([subscribe], [archive]).
 
 There is also an IRC channel to talk about using and developing
-webdriver in #ateam on irc.mozilla.org.
+webdriver in #interop on irc.mozilla.org.
 
 [subscribe]: https://lists.mozilla.org/listinfo/tools-marionette
 [archive]: https://lists.mozilla.org/pipermail/tools-marionette/
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -2105,104 +2105,16 @@ sw:
     notification_emails:
       - sw-telemetry@mozilla.com
       - echuang@mozilla.com
     release_channel_collection: opt-out
     record_in_processes:
       - 'main'
       - 'content'
 
-# The following section contains the BrowserErrorReporter scalars.
-browser.errors:
-  collected_count:
-    bug_numbers:
-      - 1444554
-    description: >
-      The count of all browser chrome JS errors that were collected locally.
-    expires: "67"
-    kind: uint
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
-  collected_with_stack_count:
-    bug_numbers:
-      - 1444554
-    description: >
-      The count of browser chrome JS errors that were collected locally and had
-      a usable stack trace.
-    expires: "67"
-    kind: uint
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
-  reported_success_count:
-    bug_numbers:
-      - 1444554
-    description: >
-      The count of all browser chrome JS errors that were reported to the
-      remote collection service.
-    expires: "67"
-    kind: uint
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
-  reported_failure_count:
-    bug_numbers:
-      - 1444554
-    description: >
-      The count of all browser chrome JS errors that we attempted to report to
-      the remote collection service, but failed to.
-    expires: "67"
-    kind: uint
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
-  sample_rate:
-    bug_numbers:
-      - 1444554
-    description: >
-      The sample rate at which collected errors were reported.
-    expires: "67"
-    kind: string
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
-  collected_count_by_filename:
-    bug_numbers:
-      - 1444554
-    description: >
-      The count of all browser chrome JS errors that were collected locally,
-      keyed by the filename of the file in which the error occurred. Collected
-      filenames are limited to specific paths under the resource:// and
-      chrome:// protocols; non-matching filenames are reported as "FILTERED".
-      Long filenames are truncated to the first 70 characters.
-    keyed: true
-    expires: "67"
-    kind: uint
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
 widget:
   ime_name_on_windows:
     bug_numbers:
       - 1215818
     description: >
       Locale ID and name of IME which was selected by users on Windows.  This
       does NOT collect legacy IMM-IME names since we cannot get readable names
       and we do not support IMM-IME so aggressively because IME vendors
--- a/tools/lint/flake8.yml
+++ b/tools/lint/flake8.yml
@@ -45,16 +45,17 @@ flake8:
         - testing/mozharness/mozharness/mozilla/tooltool.py
         - testing/mozharness/mozinfo
         - testing/mozharness/scripts
         - testing/mozharness/test
         - testing/raptor
         - testing/remotecppunittests.py
         - testing/runcppunittests.py
         - testing/talos/
+        - testing/tps/
         - testing/xpcshell
         - toolkit/components/telemetry
         - toolkit/crashreporter/tools/upload_symbols.py
         - tools/
         - xpcom/
     # Excludes should be added to topsrcdir/.flake8 due to a bug in flake8 where
     # specifying --exclude causes custom configuration files to be ignored.
     exclude: []