Bug 1439777 p2 - Remove weave:ui:* related code. r=markh,tcsc
☠☠ backed out by fea7978f3af6 ☠ ☠
authorEdouard Oger <eoger@fastmail.com>
Thu, 22 Feb 2018 16:30:39 +0800
changeset 763627 d99402ad9ecc3a33a205889459c3ab5bdf0e0c22
parent 763626 1af3426dc95663649b4b0f228e12691bfea587c9
child 763628 6e277cff709fd55706d59aaac023bb16c1390103
push id101496
push usergsquelart@mozilla.com
push dateTue, 06 Mar 2018 10:10:48 +0000
reviewersmarkh, tcsc
bugs1439777
milestone60.0a1
Bug 1439777 p2 - Remove weave:ui:* related code. r=markh,tcsc MozReview-Commit-ID: D1H36YeiJCS
browser/base/content/browser-sync.js
browser/components/customizableui/test/browser_remote_tabs_button.js
services/sync/modules/constants.js
services/sync/modules/policies.js
services/sync/services-sync.js
services/sync/tests/unit/test_errorhandler_1.js
services/sync/tests/unit/test_errorhandler_2.js
services/sync/tests/unit/test_errorhandler_filelog.js
services/sync/tests/unit/test_fxa_node_reassignment.js
services/sync/tests/unit/test_node_reassignment.js
services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
tools/lint/eslint/modules.json
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -560,17 +560,17 @@ var gSync = {
   // via the various UI components.
   doSync() {
     if (!UIState.isReady()) {
       return;
     }
     const state = UIState.get();
     if (state.status == UIState.STATUS_SIGNED_IN) {
       this.updateSyncStatus({ syncing: true });
-      setTimeout(() => Weave.Service.errorHandler.syncAndReportErrors(), 0);
+      Services.tm.dispatchToMainThread(() => Weave.Service.sync());
     }
   },
 
   openPrefs(entryPoint = "syncbutton", origin = undefined) {
     window.openPreferences("paneSync", { origin, urlParams: { entrypoint: entryPoint } });
   },
 
   openSyncedTabsPanel() {
--- a/browser/components/customizableui/test/browser_remote_tabs_button.js
+++ b/browser/components/customizableui/test/browser_remote_tabs_button.js
@@ -64,25 +64,24 @@ add_task(async function asyncCleanup() {
 
 function mockFunctions() {
   // mock UIState.get()
   UIState.get = () => ({
     status: UIState.STATUS_SIGNED_IN,
     email: "user@mozilla.com"
   });
 
-  // mock service.errorHandler.syncAndReportErrors()
-  service.errorHandler.syncAndReportErrors = mocked_syncAndReportErrors;
+  service.sync = mocked_syncAndReportErrors;
 }
 
 function mocked_syncAndReportErrors() {
   syncWasCalled = true;
 }
 
 function restoreValues() {
   UIState.get = getState;
-  service.syncAndReportErrors = originalSync;
+  service.sync = originalSync;
 }
 
 function storeInitialValues() {
   getState = UIState.get;
-  originalSync = service.syncAndReportErrors;
+  originalSync = service.sync;
 }
--- a/services/sync/modules/constants.js
+++ b/services/sync/modules/constants.js
@@ -101,17 +101,16 @@ LOGIN_FAILED_LOGIN_REJECTED:           "
 
 // sync failure status codes
 METARECORD_DOWNLOAD_FAIL:              "error.sync.reason.metarecord_download_fail",
 VERSION_OUT_OF_DATE:                   "error.sync.reason.version_out_of_date",
 CREDENTIALS_CHANGED:                   "error.sync.reason.credentials_changed",
 ABORT_SYNC_COMMAND:                    "aborting sync, process commands said so",
 NO_SYNC_NODE_FOUND:                    "error.sync.reason.no_node_found",
 OVER_QUOTA:                            "error.sync.reason.over_quota",
-PROLONGED_SYNC_FAILURE:                "error.sync.prolonged_failure",
 SERVER_MAINTENANCE:                    "error.sync.reason.serverMaintenance",
 
 RESPONSE_OVER_QUOTA:                   "14",
 
 // engine failure status codes
 ENGINE_UPLOAD_FAIL:                    "error.engine.reason.record_upload_fail",
 ENGINE_DOWNLOAD_FAIL:                  "error.engine.reason.record_download_fail",
 ENGINE_UNKNOWN_FAIL:                   "error.engine.reason.unknown_fail",
--- a/services/sync/modules/policies.js
+++ b/services/sync/modules/policies.js
@@ -649,29 +649,17 @@ SyncScheduler.prototype = {
 
 };
 
 function ErrorHandler(service) {
   this.service = service;
   this.init();
 }
 ErrorHandler.prototype = {
-  /**
-   * Flag that turns on error reporting for all errors, incl. network errors.
-   */
-  dontIgnoreErrors: false,
-
-  /**
-   * Flag that indicates if we have already reported a prolonged failure.
-   * Once set, we don't report it again, meaning this error is only reported
-   * one per run.
-   */
-  didReportProlongedError: false,
-
-  init: function init() {
+  init() {
     Svc.Obs.add("weave:engine:sync:applied", this);
     Svc.Obs.add("weave:engine:sync:error", this);
     Svc.Obs.add("weave:service:login:error", this);
     Svc.Obs.add("weave:service:sync:error", this);
     Svc.Obs.add("weave:service:sync:finish", this);
     Svc.Obs.add("weave:service:start-over:finish", this);
 
     this.initLogs();
@@ -687,17 +675,17 @@ ErrorHandler.prototype = {
 
     let logs = ["Sync", "Services.Common", "FirefoxAccounts", "Hawk",
                 "browserwindow.syncui", "BookmarkSyncUtils", "addons.xpi",
                ];
 
     this._logManager = new LogManager(Svc.Prefs, logs, "sync");
   },
 
-  observe: function observe(subject, topic, data) {
+  observe(subject, topic, data) {
     this._log.trace("Handling " + topic);
     switch (topic) {
       case "weave:engine:sync:applied":
         if (subject.newFailed) {
           // An engine isn't able to apply one or more incoming records.
           // We don't fail hard on this, but it usually indicates a bug,
           // so for now treat it as sync error (c.f. Service._syncEngine())
           Status.engines = [data, ENGINE_APPLY_FAIL];
@@ -718,24 +706,16 @@ ErrorHandler.prototype = {
           Services.telemetry.getKeyedHistogramById("WEAVE_ENGINE_SYNC_ERRORS")
                             .add(engine_name);
         }
         break;
       }
       case "weave:service:login:error":
         this._log.error("Sync encountered a login error");
         this.resetFileLog();
-
-        if (this.shouldReportError()) {
-          this.notifyOnNextTick("weave:ui:login:error");
-        } else {
-          this.notifyOnNextTick("weave:ui:clear-error");
-        }
-
-        this.dontIgnoreErrors = false;
         break;
       case "weave:service:sync:error": {
         if (Status.sync == CREDENTIALS_CHANGED) {
           this.service.logout();
         }
 
         let exception = subject;
         if (Async.isShutdownException(exception)) {
@@ -744,24 +724,16 @@ ErrorHandler.prototype = {
           this._log.error("Sync was interrupted due to the application shutting down");
           this.resetFileLog();
           break;
         }
 
         // Not a shutdown related exception...
         this._log.error("Sync encountered an error", exception);
         this.resetFileLog();
-
-        if (this.shouldReportError()) {
-          this.notifyOnNextTick("weave:ui:sync:error");
-        } else {
-          this.notifyOnNextTick("weave:ui:sync:finish");
-        }
-
-        this.dontIgnoreErrors = false;
         break;
       }
       case "weave:service:sync:finish":
         this._log.trace("Status.service is " + Status.service);
 
         // Check both of these status codes: in the event of a failure in one
         // engine, Status.service will be SYNC_FAILED_PARTIAL despite
         // Status.sync being SYNC_SUCCEEDED.
@@ -770,57 +742,26 @@ ErrorHandler.prototype = {
             Status.service == STATUS_OK) {
           // Great. Let's clear our mid-sync 401 note.
           this._log.trace("Clearing lastSyncReassigned.");
           Svc.Prefs.reset("lastSyncReassigned");
         }
 
         if (Status.service == SYNC_FAILED_PARTIAL) {
           this._log.error("Some engines did not sync correctly.");
-          this.resetFileLog();
-
-          if (this.shouldReportError()) {
-            this.dontIgnoreErrors = false;
-            this.notifyOnNextTick("weave:ui:sync:error");
-            break;
-          }
-        } else {
-          this.resetFileLog();
         }
-        this.dontIgnoreErrors = false;
-        this.notifyOnNextTick("weave:ui:sync:finish");
+        this.resetFileLog();
         break;
       case "weave:service:start-over:finish":
         // ensure we capture any logs between the last sync and the reset completing.
         this.resetFileLog();
         break;
     }
   },
 
-  notifyOnNextTick: function notifyOnNextTick(topic) {
-    CommonUtils.nextTick(function() {
-      this._log.trace("Notifying " + topic +
-                      ". Status.login is " + Status.login +
-                      ". Status.sync is " + Status.sync);
-      Svc.Obs.notify(topic);
-    }, this);
-  },
-
-  /**
-   * Trigger a sync and don't muffle any errors, particularly network errors.
-   */
-  syncAndReportErrors: function syncAndReportErrors() {
-    this._log.debug("Beginning user-triggered sync.");
-
-    this.dontIgnoreErrors = true;
-    CommonUtils.nextTick(() => {
-      this.service.sync({why: "user"});
-    }, this);
-  },
-
   async _dumpAddons() {
     // Just dump the items that sync may be concerned with. Specifically,
     // active extensions that are not hidden.
     let addons = [];
     try {
       addons = await AddonManager.getAddonsByTypes(["extension"]);
     } catch (e) {
       this._log.warn("Failed to dump addons", e);
@@ -832,118 +773,26 @@ ErrorHandler.prototype = {
       this._log.debug(" - ${name}, version ${version}, id ${id}", addon);
     }
   },
 
   /**
    * Generate a log file for the sync that just completed
    * and refresh the input & output streams.
    */
-  resetFileLog: function resetFileLog() {
-    let onComplete = logType => {
-      Svc.Obs.notify("weave:service:reset-file-log");
-      this._log.trace("Notified: " + Date.now());
-      if (logType == this._logManager.ERROR_LOG_WRITTEN) {
-        Cu.reportError("Sync encountered an error - see about:sync-log for the log file.");
-      }
-    };
-
+  async resetFileLog() {
     // If we're writing an error log, dump extensions that may be causing problems.
-    let beforeResetLog;
     if (this._logManager.sawError) {
-      beforeResetLog = this._dumpAddons();
-    } else {
-      beforeResetLog = Promise.resolve();
+      await this._dumpAddons();
     }
-    // Note we do not return the promise here - the caller doesn't need to wait
-    // for this to complete.
-    beforeResetLog
-      .then(() => this._logManager.resetFileLog())
-      .then(onComplete, onComplete);
-  },
-
-  /**
-   * Translates server error codes to meaningful strings.
-   *
-   * @param code
-   *        server error code as an integer
-   */
-  errorStr: function errorStr(code) {
-    switch (code.toString()) {
-    case "1":
-      return "illegal-method";
-    case "2":
-      return "invalid-captcha";
-    case "3":
-      return "invalid-username";
-    case "4":
-      return "cannot-overwrite-resource";
-    case "5":
-      return "userid-mismatch";
-    case "6":
-      return "json-parse-failure";
-    case "7":
-      return "invalid-password";
-    case "8":
-      return "invalid-record";
-    case "9":
-      return "weak-password";
-    default:
-      return "generic-server-error";
+    const logType = await this._logManager.resetFileLog();
+    if (logType == this._logManager.ERROR_LOG_WRITTEN) {
+      Cu.reportError("Sync encountered an error - see about:sync-log for the log file.");
     }
-  },
-
-  // A function to indicate if Sync errors should be "reported" - which in this
-  // context really means "should be notify observers of an error" - but note
-  // that since bug 1180587, no one is going to surface an error to the user.
-  shouldReportError: function shouldReportError() {
-    if (Status.login == MASTER_PASSWORD_LOCKED) {
-      this._log.trace("shouldReportError: false (master password locked).");
-      return false;
-    }
-
-    if (this.dontIgnoreErrors) {
-      return true;
-    }
-
-    if (Status.login == LOGIN_FAILED_LOGIN_REJECTED) {
-      // An explicit LOGIN_REJECTED state is always reported (bug 1081158)
-      this._log.trace("shouldReportError: true (login was rejected)");
-      return true;
-    }
-
-    let lastSync = Svc.Prefs.get("lastSync");
-    if (lastSync && ((Date.now() - Date.parse(lastSync)) >
-        Svc.Prefs.get("errorhandler.networkFailureReportTimeout") * 1000)) {
-      Status.sync = PROLONGED_SYNC_FAILURE;
-      if (this.didReportProlongedError) {
-        this._log.trace("shouldReportError: false (prolonged sync failure, but" +
-                        " we've already reported it).");
-        return false;
-      }
-      this._log.trace("shouldReportError: true (first prolonged sync failure).");
-      this.didReportProlongedError = true;
-      return true;
-    }
-
-    // We got a 401 mid-sync. Wait for the next sync before actually handling
-    // an error. This assumes that we'll get a 401 again on a login fetch in
-    // order to report the error.
-    if (!this.service.clusterURL) {
-      this._log.trace("shouldReportError: false (no cluster URL; " +
-                      "possible node reassignment).");
-      return false;
-    }
-
-
-    let result = (![Status.login, Status.sync].includes(SERVER_MAINTENANCE) &&
-                  ![Status.login, Status.sync].includes(LOGIN_FAILED_NETWORK_ERROR));
-    this._log.trace("shouldReportError: ${result} due to login=${login}, sync=${sync}",
-                    {result, login: Status.login, sync: Status.sync});
-    return result;
+    Svc.Obs.notify("weave:service:reset-file-log");
   },
 
   /**
    * Handle HTTP response results or exceptions and set the appropriate
    * Status.* bits.
    *
    * This method also looks for "side-channel" warnings.
    */
--- a/services/sync/services-sync.js
+++ b/services/sync/services-sync.js
@@ -9,18 +9,16 @@ pref("services.sync.sendVersionInfo", tr
 
 pref("services.sync.scheduler.idleInterval", 3600);  // 1 hour
 pref("services.sync.scheduler.activeInterval", 600);   // 10 minutes
 pref("services.sync.scheduler.immediateInterval", 90);    // 1.5 minutes
 pref("services.sync.scheduler.idleTime", 300);   // 5 minutes
 
 pref("services.sync.scheduler.fxa.singleDeviceInterval", 3600); // 1 hour
 
-pref("services.sync.errorhandler.networkFailureReportTimeout", 1209600); // 2 weeks
-
 // Note that new engines are typically added with a default of disabled, so
 // when an existing sync user gets the Firefox upgrade that supports the engine
 // it starts as disabled until the user has explicitly opted in.
 // The sync "create account" process typically *will* offer these engines, so
 // they may be flipped to enabled at that time.
 pref("services.sync.engine.addons", true);
 pref("services.sync.engine.addresses", false);
 pref("services.sync.engine.bookmarks", true);
--- a/services/sync/tests/unit/test_errorhandler_1.js
+++ b/services/sync/tests/unit/test_errorhandler_1.js
@@ -7,57 +7,39 @@ ChromeUtils.import("resource://services-
 ChromeUtils.import("resource://services-sync/keys.js");
 ChromeUtils.import("resource://services-sync/policies.js");
 ChromeUtils.import("resource://services-sync/service.js");
 ChromeUtils.import("resource://services-sync/status.js");
 ChromeUtils.import("resource://services-sync/util.js");
 ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
 
-var fakeServer = new SyncServer();
+const fakeServer = new SyncServer();
 fakeServer.start();
+const fakeServerUrl = "http://localhost:" + fakeServer.port;
 
 registerCleanupFunction(function() {
   return promiseStopServer(fakeServer).finally(() => {
     Svc.Prefs.resetBranch("");
   });
 });
 
-var fakeServerUrl = "http://localhost:" + fakeServer.port;
-
-const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
-
-const PROLONGED_ERROR_DURATION =
-  (Svc.Prefs.get("errorhandler.networkFailureReportTimeout") * 2) * 1000;
-
-const NON_PROLONGED_ERROR_DURATION =
-  (Svc.Prefs.get("errorhandler.networkFailureReportTimeout") / 2) * 1000;
-
-function setLastSync(lastSyncValue) {
-  Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString());
-}
-
-// This relies on Service/ErrorHandler being a singleton. Fixing this will take
-// a lot of work.
-let errorHandler = Service.errorHandler;
 let engine;
-
 add_task(async function setup() {
   await Service.engineManager.clear();
   await Service.engineManager.register(EHTestsCommon.CatapultEngine);
   engine = Service.engineManager.get("catapult");
 });
 
 async function clean() {
   let promiseLogReset = promiseOneObserver("weave:service:reset-file-log");
   await Service.startOver();
   await promiseLogReset;
   Status.resetSync();
   Status.resetBackoff();
-  errorHandler.didReportProlongedError = false;
   // Move log levels back to trace (startOver will have reversed this), sicne
   syncTestLogging();
 }
 
 add_task(async function test_401_logout() {
   enableValidationPrefs();
 
   let server = await EHTestsCommon.sync_httpd_setup();
@@ -125,525 +107,98 @@ add_task(async function test_credentials
   Assert.equal(Status.sync, CREDENTIALS_CHANGED);
   Assert.ok(!Service.isLoggedIn);
 
   // Clean up.
   await Service.startOver();
   await promiseStopServer(server);
 });
 
-add_task(function test_no_lastSync_pref() {
-  syncTestLogging();
-  // Test reported error.
-  Status.resetSync();
-  errorHandler.dontIgnoreErrors = true;
-  Status.sync = CREDENTIALS_CHANGED;
-  Assert.ok(errorHandler.shouldReportError());
-
-  // Test unreported error.
-  Status.resetSync();
-  errorHandler.dontIgnoreErrors = true;
-  Status.login = LOGIN_FAILED_NETWORK_ERROR;
-  Assert.ok(errorHandler.shouldReportError());
-
-});
-
-add_task(function test_shouldReportError() {
-  Status.login = MASTER_PASSWORD_LOCKED;
-  Assert.ok(!errorHandler.shouldReportError());
-
-  // Give ourselves a clusterURL so that the temporary 401 no-error situation
-  // doesn't come into play.
-  Service.clusterURL = fakeServerUrl;
-
-  // Test dontIgnoreErrors, non-network, non-prolonged, sync error reported
-  Status.resetSync();
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = true;
-  Status.sync = CREDENTIALS_CHANGED;
-  Assert.ok(errorHandler.shouldReportError());
-
-  // Test dontIgnoreErrors, non-network, prolonged, sync error reported
-  Status.resetSync();
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = true;
-  Status.sync = CREDENTIALS_CHANGED;
-  Assert.ok(errorHandler.shouldReportError());
-
-  // Test dontIgnoreErrors, network, non-prolonged, login error reported
-  Status.resetSync();
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = true;
-  Status.login = LOGIN_FAILED_NETWORK_ERROR;
-  Assert.ok(errorHandler.shouldReportError());
-
-  // Test dontIgnoreErrors, network, non-prolonged, sync error reported
-  Status.resetSync();
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = true;
-  Status.sync = LOGIN_FAILED_NETWORK_ERROR;
-  Assert.ok(errorHandler.shouldReportError());
-
-  // Test dontIgnoreErrors, network, prolonged, login error reported
-  Status.resetSync();
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = true;
-  Status.login = LOGIN_FAILED_NETWORK_ERROR;
-  Assert.ok(errorHandler.shouldReportError());
-
-  // Test dontIgnoreErrors, network, prolonged, sync error reported
-  Status.resetSync();
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = true;
-  Status.sync = LOGIN_FAILED_NETWORK_ERROR;
-  Assert.ok(errorHandler.shouldReportError());
-
-  // Test non-network, prolonged, sync error reported
-  Status.resetSync();
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = false;
-  errorHandler.didReportProlongedError = false;
-  Status.sync = CREDENTIALS_CHANGED;
-  Assert.ok(errorHandler.shouldReportError());
-  Assert.ok(errorHandler.didReportProlongedError);
-  errorHandler.didReportProlongedError = false;
-
-  // Test network, prolonged, login error reported
-  Status.resetSync();
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = false;
-  Status.login = LOGIN_FAILED_NETWORK_ERROR;
-  Assert.ok(errorHandler.shouldReportError());
-  Assert.ok(errorHandler.didReportProlongedError);
-  errorHandler.didReportProlongedError = false;
-
-  // Test network, prolonged, sync error reported
-  Status.resetSync();
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = false;
-  Status.sync = LOGIN_FAILED_NETWORK_ERROR;
-  Assert.ok(errorHandler.shouldReportError());
-  Assert.ok(errorHandler.didReportProlongedError);
-  errorHandler.didReportProlongedError = false;
-
-  // Test non-network, non-prolonged, sync error reported
-  Status.resetSync();
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = false;
-  Status.sync = CREDENTIALS_CHANGED;
-  Assert.ok(errorHandler.shouldReportError());
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  // Test network, non-prolonged, login error reported
-  Status.resetSync();
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = false;
-  Status.login = LOGIN_FAILED_NETWORK_ERROR;
-  Assert.ok(!errorHandler.shouldReportError());
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  // Test network, non-prolonged, sync error reported
-  Status.resetSync();
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = false;
-  Status.sync = LOGIN_FAILED_NETWORK_ERROR;
-  Assert.ok(!errorHandler.shouldReportError());
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  // Test server maintenance, sync errors are not reported
-  Status.resetSync();
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = false;
-  Status.sync = SERVER_MAINTENANCE;
-  Assert.ok(!errorHandler.shouldReportError());
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  // Test server maintenance, login errors are not reported
-  Status.resetSync();
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = false;
-  Status.login = SERVER_MAINTENANCE;
-  Assert.ok(!errorHandler.shouldReportError());
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  // Test prolonged, server maintenance, sync errors are reported
-  Status.resetSync();
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = false;
-  Status.sync = SERVER_MAINTENANCE;
-  Assert.ok(errorHandler.shouldReportError());
-  Assert.ok(errorHandler.didReportProlongedError);
-  errorHandler.didReportProlongedError = false;
-
-  // Test prolonged, server maintenance, login errors are reported
-  Status.resetSync();
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = false;
-  Status.login = SERVER_MAINTENANCE;
-  Assert.ok(errorHandler.shouldReportError());
-  Assert.ok(errorHandler.didReportProlongedError);
-  errorHandler.didReportProlongedError = false;
-
-  // Test dontIgnoreErrors, server maintenance, sync errors are reported
-  Status.resetSync();
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = true;
-  Status.sync = SERVER_MAINTENANCE;
-  Assert.ok(errorHandler.shouldReportError());
-  // dontIgnoreErrors means we don't set didReportProlongedError
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  // Test dontIgnoreErrors, server maintenance, login errors are reported
-  Status.resetSync();
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = true;
-  Status.login = SERVER_MAINTENANCE;
-  Assert.ok(errorHandler.shouldReportError());
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  // Test dontIgnoreErrors, prolonged, server maintenance,
-  // sync errors are reported
-  Status.resetSync();
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = true;
-  Status.sync = SERVER_MAINTENANCE;
-  Assert.ok(errorHandler.shouldReportError());
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  // Test dontIgnoreErrors, prolonged, server maintenance,
-  // login errors are reported
-  Status.resetSync();
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.dontIgnoreErrors = true;
-  Status.login = SERVER_MAINTENANCE;
-  Assert.ok(errorHandler.shouldReportError());
-  Assert.ok(!errorHandler.didReportProlongedError);
-});
-
-add_task(async function test_shouldReportError_master_password() {
-  _("Test error ignored due to locked master password");
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-
-  // Monkey patch Service.verifyLogin to imitate
-  // master password being locked.
-  Service._verifyLogin = Service.verifyLogin;
-  Service.verifyLogin = async function() {
-    Status.login = MASTER_PASSWORD_LOCKED;
-    return false;
-  };
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  await Service.sync();
-  Assert.ok(!errorHandler.shouldReportError());
-
-  // Clean up.
-  Service.verifyLogin = Service._verifyLogin;
-  await clean();
-  await promiseStopServer(server);
-});
-
-// Test that even if we don't have a cluster URL, a login failure due to
-// authentication errors is always reported.
-add_task(function test_shouldReportLoginFailureWithNoCluster() {
-  // Ensure no clusterURL - any error not specific to login should not be reported.
-  Service.clusterURL = "";
-
-  // Test explicit "login rejected" state.
-  Status.resetSync();
-  // If we have a LOGIN_REJECTED state, we always report the error.
-  Status.login = LOGIN_FAILED_LOGIN_REJECTED;
-  Assert.ok(errorHandler.shouldReportError());
-  // But any other status with a missing clusterURL is treated as a mid-sync
-  // 401 (ie, should be treated as a node reassignment)
-  Status.login = LOGIN_SUCCEEDED;
-  Assert.ok(!errorHandler.shouldReportError());
-});
-
-add_task(async function test_login_syncAndReportErrors_non_network_error() {
+add_task(async function test_login_non_network_error() {
   enableValidationPrefs();
 
   // Test non-network errors are reported
-  // when calling syncAndReportErrors
+  // when calling sync
   let server = await EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
   Service.identity._syncKeyBundle = null;
 
-  let promiseObserved = promiseOneObserver("weave:ui:login:error");
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.syncAndReportErrors();
-  await promiseObserved;
+  await Service.sync();
   Assert.equal(Status.login, LOGIN_FAILED_NO_PASSPHRASE);
 
   await clean();
   await promiseStopServer(server);
 });
 
-add_task(async function test_sync_syncAndReportErrors_non_network_error() {
+add_task(async function test_sync_non_network_error() {
   enableValidationPrefs();
 
   // Test non-network errors are reported
-  // when calling syncAndReportErrors
+  // when calling sync
   let server = await EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   // By calling sync, we ensure we're logged in.
   await Service.sync();
   Assert.equal(Status.sync, SYNC_SUCCEEDED);
   Assert.ok(Service.isLoggedIn);
 
   await EHTestsCommon.generateCredentialsChangedFailure();
 
-  let promiseObserved = promiseOneObserver("weave:ui:sync:error");
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  let ping = await wait_for_ping(() => errorHandler.syncAndReportErrors(), true);
+  let ping = await sync_and_validate_telem(true);
   equal(ping.status.sync, CREDENTIALS_CHANGED);
   deepEqual(ping.failureReason, {
     name: "unexpectederror",
     error: "Error: Aborting sync, remote setup failed"
   });
-  await promiseObserved;
-
-  Assert.equal(Status.sync, CREDENTIALS_CHANGED);
-  // If we clean this tick, telemetry won't get the right error
-  await Async.promiseYield();
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_login_syncAndReportErrors_prolonged_non_network_error() {
-  enableValidationPrefs();
-
-  // Test prolonged, non-network errors are
-  // reported when calling syncAndReportErrors.
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-  Service.identity._syncKeyBundle = null;
-
-  let promiseObserved = promiseOneObserver("weave:ui:login:error");
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.syncAndReportErrors();
-  await promiseObserved;
-  Assert.equal(Status.login, LOGIN_FAILED_NO_PASSPHRASE);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_sync_syncAndReportErrors_prolonged_non_network_error() {
-  enableValidationPrefs();
-
-  // Test prolonged, non-network errors are
-  // reported when calling syncAndReportErrors.
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-
-  // By calling sync, we ensure we're logged in.
-  await Service.sync();
-  Assert.equal(Status.sync, SYNC_SUCCEEDED);
-  Assert.ok(Service.isLoggedIn);
-
-  await EHTestsCommon.generateCredentialsChangedFailure();
-
-  let promiseObserved = promiseOneObserver("weave:ui:sync:error");
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  let ping = await wait_for_ping(() => errorHandler.syncAndReportErrors(), true);
-  equal(ping.status.sync, CREDENTIALS_CHANGED);
-  deepEqual(ping.failureReason, {
-    name: "unexpectederror",
-    error: "Error: Aborting sync, remote setup failed"
-  });
-  await promiseObserved;
 
   Assert.equal(Status.sync, CREDENTIALS_CHANGED);
   // If we clean this tick, telemetry won't get the right error
   await Async.promiseYield();
   await clean();
   await promiseStopServer(server);
 });
 
-add_task(async function test_login_syncAndReportErrors_network_error() {
+add_task(async function test_login_sync_network_error() {
   enableValidationPrefs();
 
-  // Test network errors are reported when calling syncAndReportErrors.
+  // Test network errors are reported when calling sync.
   await configureIdentity({username: "broken.wipe"});
   Service.clusterURL = fakeServerUrl;
 
-  let promiseObserved = promiseOneObserver("weave:ui:login:error");
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.syncAndReportErrors();
-  await promiseObserved;
-
+  await Service.sync();
   Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR);
 
   await clean();
 });
 
 
-add_task(async function test_sync_syncAndReportErrors_network_error() {
-  enableValidationPrefs();
-
-  // Test network errors are reported when calling syncAndReportErrors.
-  Services.io.offline = true;
-
-  let promiseUISyncError = promiseOneObserver("weave:ui:sync:error");
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.syncAndReportErrors();
-  await promiseUISyncError;
-  Assert.equal(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
-
-  Services.io.offline = false;
-  await clean();
-});
-
-add_task(async function test_login_syncAndReportErrors_prolonged_network_error() {
-  enableValidationPrefs();
-
-  // Test prolonged, network errors are reported
-  // when calling syncAndReportErrors.
-  await configureIdentity({username: "johndoe"});
-
-  Service.clusterURL = fakeServerUrl;
-
-  let promiseObserved = promiseOneObserver("weave:ui:login:error");
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.syncAndReportErrors();
-  await promiseObserved;
-  Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR);
-
-  await clean();
-});
-
-add_task(async function test_sync_syncAndReportErrors_prolonged_network_error() {
-  enableValidationPrefs();
-
-  // Test prolonged, network errors are reported
-  // when calling syncAndReportErrors.
-  Services.io.offline = true;
-
-  let promiseUISyncError = promiseOneObserver("weave:ui:sync:error");
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.syncAndReportErrors();
-  await promiseUISyncError;
-  Assert.equal(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
-
-  Services.io.offline = false;
-  await clean();
-});
-
-add_task(async function test_login_prolonged_non_network_error() {
+add_task(async function test_sync_network_error() {
   enableValidationPrefs();
 
-  // Test prolonged, non-network errors are reported
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-  Service.identity._syncKeyBundle = null;
-
-  let promiseObserved = promiseOneObserver("weave:ui:login:error");
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await Service.sync();
-  await promiseObserved;
-  Assert.equal(Status.sync, PROLONGED_SYNC_FAILURE);
-  Assert.ok(errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_sync_prolonged_non_network_error() {
-  enableValidationPrefs();
-
-  // Test prolonged, non-network errors are reported
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-
-  // By calling sync, we ensure we're logged in.
-  await Service.sync();
-  Assert.equal(Status.sync, SYNC_SUCCEEDED);
-  Assert.ok(Service.isLoggedIn);
-
-  await EHTestsCommon.generateCredentialsChangedFailure();
-
-  let promiseObserved = promiseOneObserver("weave:ui:sync:error");
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-
-  let ping = await sync_and_validate_telem(true);
-  equal(ping.status.sync, PROLONGED_SYNC_FAILURE);
-  deepEqual(ping.failureReason, {
-    name: "unexpectederror",
-    error: "Error: Aborting sync, remote setup failed"
-  });
-  await promiseObserved;
-  Assert.equal(Status.sync, PROLONGED_SYNC_FAILURE);
-  Assert.ok(errorHandler.didReportProlongedError);
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_login_prolonged_network_error() {
-  enableValidationPrefs();
-
-  // Test prolonged, network errors are reported
-  await configureIdentity({username: "johndoe"});
-  Service.clusterURL = fakeServerUrl;
-
-  let promiseObserved = promiseOneObserver("weave:ui:login:error");
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await Service.sync();
-  await promiseObserved;
-  Assert.equal(Status.sync, PROLONGED_SYNC_FAILURE);
-  Assert.ok(errorHandler.didReportProlongedError);
-
-  await clean();
-});
-
-add_task(async function test_sync_prolonged_network_error() {
-  enableValidationPrefs();
-
-  // Test prolonged, network errors are reported
+  // Test network errors are reported when calling sync.
   Services.io.offline = true;
 
-  let promiseUISyncError = promiseOneObserver("weave:ui:sync:error");
-
-  setLastSync(PROLONGED_ERROR_DURATION);
   await Service.sync();
-  await promiseUISyncError;
-  Assert.equal(Status.sync, PROLONGED_SYNC_FAILURE);
-  Assert.ok(errorHandler.didReportProlongedError);
+  Assert.equal(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
 
   Services.io.offline = false;
   await clean();
 });
 
 add_task(async function test_login_non_network_error() {
   enableValidationPrefs();
 
   // Test non-network errors are reported
   let server = await EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
   Service.identity._syncKeyBundle = null;
 
-  let promiseObserved = promiseOneObserver("weave:ui:login:error");
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
   await Service.sync();
-  await promiseObserved;
   Assert.equal(Status.login, LOGIN_FAILED_NO_PASSPHRASE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_sync_non_network_error() {
   enableValidationPrefs();
 
@@ -653,60 +208,46 @@ add_task(async function test_sync_non_ne
 
   // By calling sync, we ensure we're logged in.
   await Service.sync();
   Assert.equal(Status.sync, SYNC_SUCCEEDED);
   Assert.ok(Service.isLoggedIn);
 
   await EHTestsCommon.generateCredentialsChangedFailure();
 
-  let promiseObserved = promiseOneObserver("weave:ui:sync:error");
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
   await Service.sync();
-  await promiseObserved;
   Assert.equal(Status.sync, CREDENTIALS_CHANGED);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_login_network_error() {
   enableValidationPrefs();
 
   await configureIdentity({username: "johndoe"});
   Service.clusterURL = fakeServerUrl;
 
-  let promiseObserved = promiseOneObserver("weave:ui:clear-error");
   // Test network errors are not reported.
 
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
   await Service.sync();
-  await promiseObserved;
   Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   Services.io.offline = false;
   await clean();
 });
 
 add_task(async function test_sync_network_error() {
   enableValidationPrefs();
 
   // Test network errors are not reported.
   Services.io.offline = true;
 
-  let promiseSyncFinished = promiseOneObserver("weave:ui:sync:finish");
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
   await Service.sync();
-  await promiseSyncFinished;
   Assert.equal(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   Services.io.offline = false;
   await clean();
 });
 
 add_task(async function test_sync_server_maintenance_error() {
   enableValidationPrefs();
 
@@ -714,34 +255,24 @@ add_task(async function test_sync_server
   let server = await EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   const BACKOFF = 42;
   engine.enabled = true;
   engine.exception = {status: 503,
                       headers: {"retry-after": BACKOFF}};
 
-  function onSyncError() {
-    do_throw("Shouldn't get here!");
-  }
-  Svc.Obs.add("weave:ui:sync:error", onSyncError);
-
   Assert.equal(Status.service, STATUS_OK);
 
-  let promiseObserved = promiseOneObserver("weave:ui:sync:finish");
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
   let ping = await sync_and_validate_telem(true);
   equal(ping.status.sync, SERVER_MAINTENANCE);
   deepEqual(ping.engines.find(e => e.failureReason).failureReason, { name: "httperror", code: 503 });
 
-  await promiseObserved;
   Assert.equal(Status.service, SYNC_FAILED_PARTIAL);
   Assert.equal(Status.sync, SERVER_MAINTENANCE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_info_collections_login_server_maintenance_error() {
   enableValidationPrefs();
 
@@ -752,37 +283,26 @@ add_task(async function test_info_collec
   await configureIdentity({username: "broken.info"}, server);
 
   let backoffInterval;
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
     Svc.Obs.remove("weave:service:backoff:interval", observe);
     backoffInterval = subject;
   });
 
-  function onUIUpdate() {
-    do_throw("Shouldn't experience UI update!");
-  }
-  Svc.Obs.add("weave:ui:login:error", onUIUpdate);
-
   Assert.ok(!Status.enforceBackoff);
   Assert.equal(Status.service, STATUS_OK);
 
-  let promiseObserved = promiseOneObserver("weave:ui:clear-error");
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
   await Service.sync();
-  await promiseObserved;
 
   Assert.ok(Status.enforceBackoff);
   Assert.equal(backoffInterval, 42);
   Assert.equal(Status.service, LOGIN_FAILED);
   Assert.equal(Status.login, SERVER_MAINTENANCE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
-  Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_meta_global_login_server_maintenance_error() {
   enableValidationPrefs();
 
   // Test meta/global server maintenance errors are not reported.
@@ -792,32 +312,21 @@ add_task(async function test_meta_global
   await configureIdentity({username: "broken.meta"}, server);
 
   let backoffInterval;
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
     Svc.Obs.remove("weave:service:backoff:interval", observe);
     backoffInterval = subject;
   });
 
-  function onUIUpdate() {
-    do_throw("Shouldn't get here!");
-  }
-  Svc.Obs.add("weave:ui:login:error", onUIUpdate);
-
   Assert.ok(!Status.enforceBackoff);
   Assert.equal(Status.service, STATUS_OK);
 
-  let promiseObserved = promiseOneObserver("weave:ui:clear-error");
-
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
   await Service.sync();
-  await promiseObserved;
 
   Assert.ok(Status.enforceBackoff);
   Assert.equal(backoffInterval, 42);
   Assert.equal(Status.service, LOGIN_FAILED);
   Assert.equal(Status.login, SERVER_MAINTENANCE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
-  Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
   await clean();
   await promiseStopServer(server);
 });
--- a/services/sync/tests/unit/test_errorhandler_2.js
+++ b/services/sync/tests/unit/test_errorhandler_2.js
@@ -7,25 +7,26 @@ ChromeUtils.import("resource://services-
 ChromeUtils.import("resource://services-sync/keys.js");
 ChromeUtils.import("resource://services-sync/policies.js");
 ChromeUtils.import("resource://services-sync/service.js");
 ChromeUtils.import("resource://services-sync/status.js");
 ChromeUtils.import("resource://services-sync/util.js");
 ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
 
-var fakeServer = new SyncServer();
+const fakeServer = new SyncServer();
 fakeServer.start();
+const fakeServerUrl = "http://localhost:" + fakeServer.port;
 
 registerCleanupFunction(function() {
-  return promiseStopServer(fakeServer);
+  return promiseStopServer(fakeServer).finally(() => {
+    Svc.Prefs.resetBranch("");
+  });
 });
 
-var fakeServerUrl = "http://localhost:" + fakeServer.port;
-
 const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
 
 function removeLogFiles() {
   let entries = logsdir.directoryEntries;
   while (entries.hasMoreElements()) {
     let logfile = entries.getNext().QueryInterface(Ci.nsIFile);
     logfile.remove(false);
   }
@@ -35,61 +36,29 @@ function getLogFiles() {
   let result = [];
   let entries = logsdir.directoryEntries;
   while (entries.hasMoreElements()) {
     result.push(entries.getNext().QueryInterface(Ci.nsIFile));
   }
   return result;
 }
 
-const PROLONGED_ERROR_DURATION =
-  (Svc.Prefs.get("errorhandler.networkFailureReportTimeout") * 2) * 1000;
-
-const NON_PROLONGED_ERROR_DURATION =
-  (Svc.Prefs.get("errorhandler.networkFailureReportTimeout") / 2) * 1000;
-
-function setLastSync(lastSyncValue) {
-  Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString());
-}
-
-// This relies on Service/ErrorHandler being a singleton. Fixing this will take
-// a lot of work.
-var errorHandler = Service.errorHandler;
 let engine;
-
-async function syncAndWait(topic) {
-  let promise1 = promiseOneObserver(topic);
-  // also wait for the log file to be written
-  let promise2 = promiseOneObserver("weave:service:reset-file-log");
-  await Service.sync();
-  await promise1;
-  await promise2;
-}
-
-async function syncAndReportErrorsAndWait(topic) {
-  let promise1 = promiseOneObserver(topic);
-  // also wait for the log file to be written
-  let promise2 = promiseOneObserver("weave:service:reset-file-log");
-  errorHandler.syncAndReportErrors();
-  await promise1;
-  await promise2;
-}
 add_task(async function setup() {
   await Service.engineManager.clear();
   await Service.engineManager.register(EHTestsCommon.CatapultEngine);
   engine = Service.engineManager.get("catapult");
 });
 
 async function clean() {
   let promiseLogReset = promiseOneObserver("weave:service:reset-file-log");
   await Service.startOver();
   await promiseLogReset;
   Status.resetSync();
   Status.resetBackoff();
-  errorHandler.didReportProlongedError = false;
   removeLogFiles();
   // Move log levels back to trace (startOver will have reversed this), sicne
   syncTestLogging();
 }
 
 add_task(async function test_crypto_keys_login_server_maintenance_error() {
   enableValidationPrefs();
 
@@ -104,34 +73,26 @@ add_task(async function test_crypto_keys
   Service.collectionKeys.clear();
 
   let backoffInterval;
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
     Svc.Obs.remove("weave:service:backoff:interval", observe);
     backoffInterval = subject;
   });
 
-  function onUIUpdate() {
-    do_throw("Shouldn't get here!");
-  }
-  Svc.Obs.add("weave:ui:login:error", onUIUpdate);
-
   Assert.ok(!Status.enforceBackoff);
   Assert.equal(Status.service, STATUS_OK);
 
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  await syncAndWait("weave:ui:clear-error");
+  await Service.sync();
 
   Assert.ok(Status.enforceBackoff);
   Assert.equal(backoffInterval, 42);
   Assert.equal(Status.service, LOGIN_FAILED);
   Assert.equal(Status.login, SERVER_MAINTENANCE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
-  Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_lastSync_not_updated_on_complete_failure() {
   enableValidationPrefs();
 
   // Test info/collections prolonged server maintenance errors are reported.
@@ -161,235 +122,35 @@ add_task(async function test_lastSync_no
 
   // We shouldn't update lastSync on complete failure.
   Assert.equal(lastSync, Svc.Prefs.get("lastSync"));
 
   await clean();
   await promiseStopServer(server);
 });
 
-add_task(async function test_info_collections_login_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test info/collections prolonged server maintenance errors are reported.
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-
-  await configureIdentity({username: "broken.info"}, server);
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndWait("weave:ui:login:error");
-
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, SYNC_FAILED);
-  Assert.equal(Status.sync, PROLONGED_SYNC_FAILURE);
-  Assert.ok(errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_meta_global_login_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test meta/global prolonged server maintenance errors are reported.
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-
-  await configureIdentity({username: "broken.meta"}, server);
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndWait("weave:ui:login:error");
-
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, SYNC_FAILED);
-  Assert.equal(Status.sync, PROLONGED_SYNC_FAILURE);
-  Assert.ok(errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_download_crypto_keys_login_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test crypto/keys prolonged server maintenance errors are reported.
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-
-  await configureIdentity({username: "broken.keys"}, server);
-  // Force re-download of keys
-  Service.collectionKeys.clear();
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndWait("weave:ui:login:error");
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, SYNC_FAILED);
-  Assert.equal(Status.sync, PROLONGED_SYNC_FAILURE);
-  Assert.ok(errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_upload_crypto_keys_login_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test crypto/keys prolonged server maintenance errors are reported.
-  let server = await EHTestsCommon.sync_httpd_setup();
-
-  // Start off with an empty account, do not upload a key.
-  await configureIdentity({username: "broken.keys"}, server);
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndWait("weave:ui:login:error");
-
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, SYNC_FAILED);
-  Assert.equal(Status.sync, PROLONGED_SYNC_FAILURE);
-  Assert.ok(errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_wipeServer_login_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test that we report prolonged server maintenance errors that occur whilst
-  // wiping the server.
-  let server = await EHTestsCommon.sync_httpd_setup();
-
-  // Start off with an empty account, do not upload a key.
-  await configureIdentity({username: "broken.wipe"}, server);
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndWait("weave:ui:login:error");
-
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, SYNC_FAILED);
-  Assert.equal(Status.sync, PROLONGED_SYNC_FAILURE);
-  Assert.ok(errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_wipeRemote_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test that we report prolonged server maintenance errors that occur whilst
-  // wiping all remote devices.
-  let server = await EHTestsCommon.sync_httpd_setup();
-
-  server.registerPathHandler("/1.1/broken.wipe/storage/catapult", EHTestsCommon.service_unavailable);
-  await configureIdentity({username: "broken.wipe"}, server);
-  await EHTestsCommon.generateAndUploadKeys();
-
-  engine.exception = null;
-  engine.enabled = true;
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  let promiseObserved = promiseOneObserver("weave:ui:sync:error");
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  Svc.Prefs.set("firstSync", "wipeRemote");
-  setLastSync(PROLONGED_ERROR_DURATION);
-  let ping = await sync_and_validate_telem(true);
-  deepEqual(ping.failureReason, { name: "httperror", code: 503 });
-  await promiseObserved;
-
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, SYNC_FAILED);
-  Assert.equal(Status.sync, PROLONGED_SYNC_FAILURE);
-  Assert.equal(Svc.Prefs.get("firstSync"), "wipeRemote");
-  Assert.ok(errorHandler.didReportProlongedError);
-  await promiseStopServer(server);
-  await clean();
-});
-
 add_task(async function test_sync_syncAndReportErrors_server_maintenance_error() {
   enableValidationPrefs();
 
   // Test server maintenance errors are reported
   // when calling syncAndReportErrors.
   let server = await EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   const BACKOFF = 42;
   engine.enabled = true;
   engine.exception = {status: 503,
                       headers: {"retry-after": BACKOFF}};
 
   Assert.equal(Status.service, STATUS_OK);
 
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:sync:error");
+  await Service.sync();
 
   Assert.equal(Status.service, SYNC_FAILED_PARTIAL);
   Assert.equal(Status.sync, SERVER_MAINTENANCE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_info_collections_login_syncAndReportErrors_server_maintenance_error() {
   enableValidationPrefs();
 
@@ -404,24 +165,22 @@ add_task(async function test_info_collec
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
     Svc.Obs.remove("weave:service:backoff:interval", observe);
     backoffInterval = subject;
   });
 
   Assert.ok(!Status.enforceBackoff);
   Assert.equal(Status.service, STATUS_OK);
 
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:login:error");
+  await Service.sync();
 
   Assert.ok(Status.enforceBackoff);
   Assert.equal(backoffInterval, 42);
   Assert.equal(Status.service, LOGIN_FAILED);
   Assert.equal(Status.login, SERVER_MAINTENANCE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_meta_global_login_syncAndReportErrors_server_maintenance_error() {
   enableValidationPrefs();
 
@@ -436,24 +195,22 @@ add_task(async function test_meta_global
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
     Svc.Obs.remove("weave:service:backoff:interval", observe);
     backoffInterval = subject;
   });
 
   Assert.ok(!Status.enforceBackoff);
   Assert.equal(Status.service, STATUS_OK);
 
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:login:error");
+  await Service.sync();
 
   Assert.ok(Status.enforceBackoff);
   Assert.equal(backoffInterval, 42);
   Assert.equal(Status.service, LOGIN_FAILED);
   Assert.equal(Status.login, SERVER_MAINTENANCE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_download_crypto_keys_login_syncAndReportErrors_server_maintenance_error() {
   enableValidationPrefs();
 
@@ -470,24 +227,22 @@ add_task(async function test_download_cr
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
     Svc.Obs.remove("weave:service:backoff:interval", observe);
     backoffInterval = subject;
   });
 
   Assert.ok(!Status.enforceBackoff);
   Assert.equal(Status.service, STATUS_OK);
 
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:login:error");
+  await Service.sync();
 
   Assert.ok(Status.enforceBackoff);
   Assert.equal(backoffInterval, 42);
   Assert.equal(Status.service, LOGIN_FAILED);
   Assert.equal(Status.login, SERVER_MAINTENANCE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_upload_crypto_keys_login_syncAndReportErrors_server_maintenance_error() {
   enableValidationPrefs();
 
@@ -502,24 +257,22 @@ add_task(async function test_upload_cryp
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
     Svc.Obs.remove("weave:service:backoff:interval", observe);
     backoffInterval = subject;
   });
 
   Assert.ok(!Status.enforceBackoff);
   Assert.equal(Status.service, STATUS_OK);
 
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:login:error");
+  await Service.sync();
 
   Assert.ok(Status.enforceBackoff);
   Assert.equal(backoffInterval, 42);
   Assert.equal(Status.service, LOGIN_FAILED);
   Assert.equal(Status.login, SERVER_MAINTENANCE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_wipeServer_login_syncAndReportErrors_server_maintenance_error() {
   enableValidationPrefs();
 
@@ -534,24 +287,22 @@ add_task(async function test_wipeServer_
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
     Svc.Obs.remove("weave:service:backoff:interval", observe);
     backoffInterval = subject;
   });
 
   Assert.ok(!Status.enforceBackoff);
   Assert.equal(Status.service, STATUS_OK);
 
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:login:error");
+  await Service.sync();
 
   Assert.ok(Status.enforceBackoff);
   Assert.equal(backoffInterval, 42);
   Assert.equal(Status.service, LOGIN_FAILED);
   Assert.equal(Status.login, SERVER_MAINTENANCE);
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_wipeRemote_syncAndReportErrors_server_maintenance_error() {
   enableValidationPrefs();
 
@@ -570,225 +321,23 @@ add_task(async function test_wipeRemote_
     Svc.Obs.remove("weave:service:backoff:interval", observe);
     backoffInterval = subject;
   });
 
   Assert.ok(!Status.enforceBackoff);
   Assert.equal(Status.service, STATUS_OK);
 
   Svc.Prefs.set("firstSync", "wipeRemote");
-  setLastSync(NON_PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:sync:error");
+  await Service.sync();
 
   Assert.ok(Status.enforceBackoff);
   Assert.equal(backoffInterval, 42);
   Assert.equal(Status.service, SYNC_FAILED);
   Assert.equal(Status.sync, SERVER_MAINTENANCE);
   Assert.equal(Svc.Prefs.get("firstSync"), "wipeRemote");
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_sync_syncAndReportErrors_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test prolonged server maintenance errors are
-  // reported when calling syncAndReportErrors.
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-
-  const BACKOFF = 42;
-  engine.enabled = true;
-  engine.exception = {status: 503,
-                      headers: {"retry-after": BACKOFF}};
-
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:sync:error");
-
-  Assert.equal(Status.service, SYNC_FAILED_PARTIAL);
-  Assert.equal(Status.sync, SERVER_MAINTENANCE);
-  // syncAndReportErrors means dontIgnoreErrors, which means
-  // didReportProlongedError not touched.
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_info_collections_login_syncAndReportErrors_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test info/collections server maintenance errors are reported
-  // when calling syncAndReportErrors.
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-
-  await configureIdentity({username: "broken.info"}, server);
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:login:error");
-
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, LOGIN_FAILED);
-  Assert.equal(Status.login, SERVER_MAINTENANCE);
-  // syncAndReportErrors means dontIgnoreErrors, which means
-  // didReportProlongedError not touched.
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_meta_global_login_syncAndReportErrors_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test meta/global server maintenance errors are reported
-  // when calling syncAndReportErrors.
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-
-  await configureIdentity({username: "broken.meta"}, server);
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:login:error");
-
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, LOGIN_FAILED);
-  Assert.equal(Status.login, SERVER_MAINTENANCE);
-  // syncAndReportErrors means dontIgnoreErrors, which means
-  // didReportProlongedError not touched.
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_download_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test crypto/keys server maintenance errors are reported
-  // when calling syncAndReportErrors.
-  let server = await EHTestsCommon.sync_httpd_setup();
-  await EHTestsCommon.setUp(server);
-
-  await configureIdentity({username: "broken.keys"}, server);
-  // Force re-download of keys
-  Service.collectionKeys.clear();
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:login:error");
-
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, LOGIN_FAILED);
-  Assert.equal(Status.login, SERVER_MAINTENANCE);
-  // syncAndReportErrors means dontIgnoreErrors, which means
-  // didReportProlongedError not touched.
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_upload_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test crypto/keys server maintenance errors are reported
-  // when calling syncAndReportErrors.
-  let server = await EHTestsCommon.sync_httpd_setup();
-
-  // Start off with an empty account, do not upload a key.
-  await configureIdentity({username: "broken.keys"}, server);
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:login:error");
-
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, LOGIN_FAILED);
-  Assert.equal(Status.login, SERVER_MAINTENANCE);
-  // syncAndReportErrors means dontIgnoreErrors, which means
-  // didReportProlongedError not touched.
-  Assert.ok(!errorHandler.didReportProlongedError);
-
-  await clean();
-  await promiseStopServer(server);
-});
-
-add_task(async function test_wipeServer_login_syncAndReportErrors_prolonged_server_maintenance_error() {
-  enableValidationPrefs();
-
-  // Test crypto/keys server maintenance errors are reported
-  // when calling syncAndReportErrors.
-  let server = await EHTestsCommon.sync_httpd_setup();
-
-  // Start off with an empty account, do not upload a key.
-  await configureIdentity({username: "broken.wipe"}, server);
-
-  let backoffInterval;
-  Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
-    Svc.Obs.remove("weave:service:backoff:interval", observe);
-    backoffInterval = subject;
-  });
-
-  Assert.ok(!Status.enforceBackoff);
-  Assert.equal(Status.service, STATUS_OK);
-
-  setLastSync(PROLONGED_ERROR_DURATION);
-  await syncAndReportErrorsAndWait("weave:ui:login:error");
-
-  Assert.ok(Status.enforceBackoff);
-  Assert.equal(backoffInterval, 42);
-  Assert.equal(Status.service, LOGIN_FAILED);
-  Assert.equal(Status.login, SERVER_MAINTENANCE);
-  // syncAndReportErrors means dontIgnoreErrors, which means
-  // didReportProlongedError not touched.
-  Assert.ok(!errorHandler.didReportProlongedError);
 
   await clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_sync_engine_generic_fail() {
   enableValidationPrefs();
 
@@ -840,55 +389,53 @@ add_task(async function test_sync_engine
 
   let syncErrors = sumHistogram("WEAVE_ENGINE_SYNC_ERRORS", { key: "catapult" });
   Assert.ok(syncErrors, 1);
 
   await clean();
   await promiseStopServer(server);
 });
 
-add_task(async function test_logs_on_sync_error_despite_shouldReportError() {
+add_task(async function test_logs_on_sync_error() {
   enableValidationPrefs();
 
   _("Ensure that an error is still logged when weave:service:sync:error " +
     "is notified, despite shouldReportError returning false.");
 
   let log = Log.repository.getLogger("Sync.ErrorHandler");
   Svc.Prefs.set("log.appender.file.logOnError", true);
   log.info("TESTING");
 
   // Ensure that we report no error.
   Status.login = MASTER_PASSWORD_LOCKED;
-  Assert.ok(!errorHandler.shouldReportError());
 
   let promiseObserved = promiseOneObserver("weave:service:reset-file-log");
   Svc.Obs.notify("weave:service:sync:error", {});
   await promiseObserved;
 
   // Test that error log was written.
   let logFiles = getLogFiles();
   equal(logFiles.length, 1);
   Assert.ok(logFiles[0].leafName.startsWith("error-sync-"), logFiles[0].leafName);
 
   await clean();
 });
 
-add_task(async function test_logs_on_login_error_despite_shouldReportError() {
+add_task(async function test_logs_on_login_error() {
   enableValidationPrefs();
 
   _("Ensure that an error is still logged when weave:service:login:error " +
     "is notified, despite shouldReportError returning false.");
 
   let log = Log.repository.getLogger("Sync.ErrorHandler");
   Svc.Prefs.set("log.appender.file.logOnError", true);
   log.info("TESTING");
 
   // Ensure that we report no error.
   Status.login = MASTER_PASSWORD_LOCKED;
-  Assert.ok(!errorHandler.shouldReportError());
 
   let promiseObserved = promiseOneObserver("weave:service:reset-file-log");
   Svc.Obs.notify("weave:service:login:error", {});
   await promiseObserved;
 
   // Test that error log was written.
   let logFiles = getLogFiles();
   equal(logFiles.length, 1);
--- a/services/sync/tests/unit/test_errorhandler_filelog.js
+++ b/services/sync/tests/unit/test_errorhandler_filelog.js
@@ -13,25 +13,18 @@ const logsdir            = FileUtils.get
 
 // Delay to wait before cleanup, to allow files to age.
 // This is so large because the file timestamp granularity is per-second, and
 // so otherwise we can end up with all of our files -- the ones we want to
 // keep, and the ones we want to clean up -- having the same modified time.
 const CLEANUP_DELAY      = 2000;
 const DELAY_BUFFER       = 500; // Buffer for timers on different OS platforms.
 
-const PROLONGED_ERROR_DURATION =
-  (Svc.Prefs.get("errorhandler.networkFailureReportTimeout") * 2) * 1000;
-
 var errorHandler = Service.errorHandler;
 
-function setLastSync(lastSyncValue) {
-  Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString());
-}
-
 function run_test() {
   validate_all_future_pings();
   run_next_test();
 }
 
 add_test(function test_noOutput() {
   // Ensure that the log appender won't print anything.
   errorHandler._logManager._fileAppender.level = Log.Level.Fatal + 1;
@@ -137,18 +130,17 @@ add_test(function test_sync_error_logOnE
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
     // No log file was written.
     Assert.ok(!logsdir.directoryEntries.hasMoreElements());
 
     Svc.Prefs.resetBranch("");
     run_next_test();
   });
 
-  // Fake an unsuccessful sync due to prolonged failure.
-  setLastSync(PROLONGED_ERROR_DURATION);
+  // Fake an unsuccessful sync.
   Svc.Obs.notify("weave:service:sync:error");
 });
 
 add_test(function test_sync_error_logOnError_true() {
   Svc.Prefs.set("log.appender.file.logOnError", true);
 
   let log = Log.repository.getLogger("Sync.Test.FileLog");
   const MESSAGE = "this WILL show up";
@@ -178,18 +170,17 @@ add_test(function test_sync_error_logOnE
         // Stupid Windows box.
       }
 
       Svc.Prefs.resetBranch("");
       run_next_test();
     });
   });
 
-  // Fake an unsuccessful sync due to prolonged failure.
-  setLastSync(PROLONGED_ERROR_DURATION);
+  // Fake an unsuccessful sync.
   Svc.Obs.notify("weave:service:sync:error");
 });
 
 add_test(function test_login_error_logOnError_false() {
   Svc.Prefs.set("log.appender.file.logOnError", false);
 
   let log = Log.repository.getLogger("Sync.Test.FileLog");
   log.info("this won't show up");
@@ -198,18 +189,17 @@ add_test(function test_login_error_logOn
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
     // No log file was written.
     Assert.ok(!logsdir.directoryEntries.hasMoreElements());
 
     Svc.Prefs.resetBranch("");
     run_next_test();
   });
 
-  // Fake an unsuccessful login due to prolonged failure.
-  setLastSync(PROLONGED_ERROR_DURATION);
+  // Fake an unsuccessful login.
   Svc.Obs.notify("weave:service:login:error");
 });
 
 add_test(function test_login_error_logOnError_true() {
   Svc.Prefs.set("log.appender.file.logOnError", true);
 
   let log = Log.repository.getLogger("Sync.Test.FileLog");
   const MESSAGE = "this WILL show up";
@@ -239,18 +229,17 @@ add_test(function test_login_error_logOn
         // Stupid Windows box.
       }
 
       Svc.Prefs.resetBranch("");
       run_next_test();
     });
   });
 
-  // Fake an unsuccessful login due to prolonged failure.
-  setLastSync(PROLONGED_ERROR_DURATION);
+  // Fake an unsuccessful login.
   Svc.Obs.notify("weave:service:login:error");
 });
 
 add_test(function test_noNewFailed_noErrorLog() {
   Svc.Prefs.set("log.appender.file.logOnError", true);
   Svc.Prefs.set("log.appender.file.logOnSuccess", false);
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
@@ -348,18 +337,17 @@ add_test(function test_errorLog_dumpAddo
         // Stupid Windows box.
       }
 
       Svc.Prefs.resetBranch("");
       run_next_test();
     });
   });
 
-  // Fake an unsuccessful sync due to prolonged failure.
-  setLastSync(PROLONGED_ERROR_DURATION);
+  // Fake an unsuccessful sync.
   Svc.Obs.notify("weave:service:sync:error");
 });
 
 // Check that error log files are deleted above an age threshold.
 add_test(function test_logErrorCleanup_age() {
   _("Beginning test_logErrorCleanup_age.");
   let maxAge = CLEANUP_DELAY / 1000;
   let oldLogs = [];
--- a/services/sync/tests/unit/test_fxa_node_reassignment.js
+++ b/services/sync/tests/unit/test_fxa_node_reassignment.js
@@ -20,23 +20,16 @@ ChromeUtils.import("resource://gre/modul
 
 add_task(async function setup() {
   // Disables all built-in engines. Important for avoiding errors thrown by the
   // add-ons engine.
   await Service.engineManager.clear();
 
   // Setup the FxA identity manager and cluster manager.
   Status.__authManager = Service.identity = new BrowserIDManager();
-
-  // None of the failures in this file should result in a UI error.
-  function onUIError() {
-    do_throw("Errors should not be presented in the UI.");
-  }
-  Svc.Obs.add("weave:ui:login:error", onUIError);
-  Svc.Obs.add("weave:ui:sync:error", onUIError);
 });
 
 
 // API-compatible with SyncServer handler. Bind `handler` to something to use
 // as a ServerCollection handler.
 function handleReassign(handler, req, resp) {
   resp.setStatusLine(req.httpVersion, 401, "Node reassignment");
   resp.setHeader("Content-Type", "application/json");
--- a/services/sync/tests/unit/test_node_reassignment.js
+++ b/services/sync/tests/unit/test_node_reassignment.js
@@ -11,23 +11,16 @@ ChromeUtils.import("resource://services-
 ChromeUtils.import("resource://services-sync/status.js");
 ChromeUtils.import("resource://services-sync/util.js");
 ChromeUtils.import("resource://testing-common/services/sync/rotaryengine.js");
 ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
 
 
 add_task(async function setup() {
   validate_all_future_pings();
-
-  // None of the failures in this file should result in a UI error.
-  function onUIError() {
-    do_throw("Errors should not be presented in the UI.");
-  }
-  Svc.Obs.add("weave:ui:login:error", onUIError);
-  Svc.Obs.add("weave:ui:sync:error", onUIError);
 });
 
 /**
  * Emulate the following Zeus config:
  * $draining = data.get($prefix . $host . " draining");
  * if ($draining == "drain.") {
  *   log.warn($log_host_db_status . " migrating=1 (node-reassignment)" .
  *            $log_suffix);
--- a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
+++ b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
@@ -215,21 +215,18 @@ add_task(async function test_disabledLoc
   await setUp(server);
 
   _("Disable engine locally.");
   Service._ignorePrefObserver = true;
   engine.enabled = true;
   Service._ignorePrefObserver = false;
   engine.enabled = false;
 
-  let promiseObserved = promiseOneObserver("weave:ui:sync:error");
-
   _("Sync.");
-  Service.errorHandler.syncAndReportErrors();
-  await promiseObserved;
+  await Service.sync();
   Assert.equal(Service.status.sync, SERVER_MAINTENANCE);
 
   await Service.startOver();
   await promiseStopServer(server);
 });
 
 add_task(async function test_enabledRemotely() {
   enableValidationPrefs();
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -24,17 +24,17 @@
   "BootstrapMonitor.jsm": ["monitor"],
   "browser-loader.js": ["BrowserLoader"],
   "browserid_identity.js": ["BrowserIDManager", "AuthenticationError"],
   "CertUtils.jsm": ["BadCertHandler", "checkCert", "readCertPrefs", "validateCert"],
   "clients.js": ["ClientEngine", "ClientsRec"],
   "collection_repair.js": ["getRepairRequestor", "getAllRepairRequestors", "CollectionRepairRequestor", "getRepairResponder", "CollectionRepairResponder"],
   "collection_validator.js": ["CollectionValidator", "CollectionProblemData"],
   "Console.jsm": ["console", "ConsoleAPI"],
-  "constants.js": ["WEAVE_VERSION", "SYNC_API_VERSION", "STORAGE_VERSION", "PREFS_BRANCH", "DEFAULT_KEYBUNDLE_NAME", "SYNC_KEY_ENCODED_LENGTH", "SYNC_KEY_DECODED_LENGTH", "NO_SYNC_NODE_INTERVAL", "MAX_ERROR_COUNT_BEFORE_BACKOFF", "MINIMUM_BACKOFF_INTERVAL", "MAXIMUM_BACKOFF_INTERVAL", "HMAC_EVENT_INTERVAL", "MASTER_PASSWORD_LOCKED_RETRY_INTERVAL", "DEFAULT_GUID_FETCH_BATCH_SIZE", "DEFAULT_DOWNLOAD_BATCH_SIZE", "SINGLE_USER_THRESHOLD", "MULTI_DEVICE_THRESHOLD", "SCORE_INCREMENT_SMALL", "SCORE_INCREMENT_MEDIUM", "SCORE_INCREMENT_XLARGE", "SCORE_UPDATE_DELAY", "IDLE_OBSERVER_BACK_DELAY", "URI_LENGTH_MAX", "MAX_HISTORY_UPLOAD", "MAX_HISTORY_DOWNLOAD", "STATUS_OK", "SYNC_FAILED", "LOGIN_FAILED", "SYNC_FAILED_PARTIAL", "CLIENT_NOT_CONFIGURED", "STATUS_DISABLED", "MASTER_PASSWORD_LOCKED", "LOGIN_SUCCEEDED", "SYNC_SUCCEEDED", "ENGINE_SUCCEEDED", "LOGIN_FAILED_NO_USERNAME", "LOGIN_FAILED_NO_PASSPHRASE", "LOGIN_FAILED_NETWORK_ERROR", "LOGIN_FAILED_SERVER_ERROR", "LOGIN_FAILED_INVALID_PASSPHRASE", "LOGIN_FAILED_LOGIN_REJECTED", "METARECORD_DOWNLOAD_FAIL", "VERSION_OUT_OF_DATE", "CREDENTIALS_CHANGED", "ABORT_SYNC_COMMAND", "NO_SYNC_NODE_FOUND", "OVER_QUOTA", "PROLONGED_SYNC_FAILURE", "SERVER_MAINTENANCE", "RESPONSE_OVER_QUOTA", "ENGINE_UPLOAD_FAIL", "ENGINE_DOWNLOAD_FAIL", "ENGINE_UNKNOWN_FAIL", "ENGINE_APPLY_FAIL", "ENGINE_BATCH_INTERRUPTED", "kSyncMasterPasswordLocked", "kSyncWeaveDisabled", "kSyncNetworkOffline", "kSyncBackoffNotMet", "kFirstSyncChoiceNotMade", "kSyncNotConfigured", "kFirefoxShuttingDown", "DEVICE_TYPE_DESKTOP", "DEVICE_TYPE_MOBILE", "SQLITE_MAX_VARIABLE_NUMBER"],
+  "constants.js": ["WEAVE_VERSION", "SYNC_API_VERSION", "STORAGE_VERSION", "PREFS_BRANCH", "DEFAULT_KEYBUNDLE_NAME", "SYNC_KEY_ENCODED_LENGTH", "SYNC_KEY_DECODED_LENGTH", "NO_SYNC_NODE_INTERVAL", "MAX_ERROR_COUNT_BEFORE_BACKOFF", "MINIMUM_BACKOFF_INTERVAL", "MAXIMUM_BACKOFF_INTERVAL", "HMAC_EVENT_INTERVAL", "MASTER_PASSWORD_LOCKED_RETRY_INTERVAL", "DEFAULT_GUID_FETCH_BATCH_SIZE", "DEFAULT_DOWNLOAD_BATCH_SIZE", "SINGLE_USER_THRESHOLD", "MULTI_DEVICE_THRESHOLD", "SCORE_INCREMENT_SMALL", "SCORE_INCREMENT_MEDIUM", "SCORE_INCREMENT_XLARGE", "SCORE_UPDATE_DELAY", "IDLE_OBSERVER_BACK_DELAY", "URI_LENGTH_MAX", "MAX_HISTORY_UPLOAD", "MAX_HISTORY_DOWNLOAD", "STATUS_OK", "SYNC_FAILED", "LOGIN_FAILED", "SYNC_FAILED_PARTIAL", "CLIENT_NOT_CONFIGURED", "STATUS_DISABLED", "MASTER_PASSWORD_LOCKED", "LOGIN_SUCCEEDED", "SYNC_SUCCEEDED", "ENGINE_SUCCEEDED", "LOGIN_FAILED_NO_USERNAME", "LOGIN_FAILED_NO_PASSPHRASE", "LOGIN_FAILED_NETWORK_ERROR", "LOGIN_FAILED_SERVER_ERROR", "LOGIN_FAILED_INVALID_PASSPHRASE", "LOGIN_FAILED_LOGIN_REJECTED", "METARECORD_DOWNLOAD_FAIL", "VERSION_OUT_OF_DATE", "CREDENTIALS_CHANGED", "ABORT_SYNC_COMMAND", "NO_SYNC_NODE_FOUND", "OVER_QUOTA", "SERVER_MAINTENANCE", "RESPONSE_OVER_QUOTA", "ENGINE_UPLOAD_FAIL", "ENGINE_DOWNLOAD_FAIL", "ENGINE_UNKNOWN_FAIL", "ENGINE_APPLY_FAIL", "ENGINE_BATCH_INTERRUPTED", "kSyncMasterPasswordLocked", "kSyncWeaveDisabled", "kSyncNetworkOffline", "kSyncBackoffNotMet", "kFirstSyncChoiceNotMade", "kSyncNotConfigured", "kFirefoxShuttingDown", "DEVICE_TYPE_DESKTOP", "DEVICE_TYPE_MOBILE", "SQLITE_MAX_VARIABLE_NUMBER"],
   "Constants.jsm": ["Roles", "Events", "Relations", "Filters", "States", "Prefilters"],
   "ContactDB.jsm": ["ContactDB", "DB_NAME", "STORE_NAME", "SAVED_GETALL_STORE_NAME", "REVISION_STORE", "DB_VERSION"],
   "content-server.jsm": ["init"],
   "content.jsm": ["registerContentFrame"],
   "ContentCrashHandlers.jsm": ["TabCrashHandler", "PluginCrashReporter", "UnsubmittedCrashHandler"],
   "ContentObservers.js": [],
   "ContentPrefUtils.jsm": ["ContentPref", "cbHandleResult", "cbHandleError", "cbHandleCompletion", "safeCallback", "_methodsCallableFromChild"],
   "cookies.js": ["Cookies"],