Bug 1383622 (part 1) - convert most of TPS to async/await. r?tcsc draft
authorMark Hammond <mhammond@skippinet.com.au>
Sat, 12 Aug 2017 14:09:48 +1000
changeset 647789 1a9e739d894d2bbe2d99a6a73a2a5ff8ed41598e
parent 647785 1d38626ba9686d119e489db538ce84f8f9854217
child 647790 6dcd3a71a484c888a2b27d2a9f86d07a20620cdd
child 647816 c1e6d3efbf3075488f0ab1891b90efc9c0db7c96
push id74541
push userbmo:markh@mozilla.com
push dateWed, 16 Aug 2017 23:07:18 +0000
reviewerstcsc
bugs1383622
milestone57.0a1
Bug 1383622 (part 1) - convert most of TPS to async/await. r?tcsc MozReview-Commit-ID: F6kBw1vPBPh
services/sync/tps/extensions/tps/components/tps-cmdline.js
services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
services/sync/tps/extensions/tps/resource/tps.jsm
--- a/services/sync/tps/extensions/tps/components/tps-cmdline.js
+++ b/services/sync/tps/extensions/tps/components/tps-cmdline.js
@@ -16,16 +16,17 @@ const nsICmdLineHandler              = C
 const nsICommandLine                 = Components.interfaces.nsICommandLine;
 const nsICommandLineHandler          = Components.interfaces.nsICommandLineHandler;
 const nsIComponentRegistrar          = Components.interfaces.nsIComponentRegistrar;
 const nsISupportsString              = Components.interfaces.nsISupportsString;
 const nsIWindowWatcher               = Components.interfaces.nsIWindowWatcher;
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/osfile.jsm");
 
 function TPSCmdLineHandler() {}
 
 TPSCmdLineHandler.prototype = {
   classDescription: "TPSCmdLineHandler",
   classID: TPS_CMDLINE_CLSID,
   contractID: TPS_CMDLINE_CONTRACTID,
 
@@ -52,28 +53,28 @@ TPSCmdLineHandler.prototype = {
     if (phase == null)
         throw Error("must specify --tpsphase with --tps");
     let logfile = cmdLine.handleFlagWithParam("tpslogfile", false);
     if (logfile == null)
         logfile = "";
 
     options.ignoreUnusedEngines = cmdLine.handleFlag("ignore-unused-engines",
                                                      false);
-    let uri = cmdLine.resolveURI(uristr).spec;
+    let uri = cmdLine.resolveURI(OS.Path.normalize(uristr)).spec;
 
     const onStartupFinished = () => {
       Services.obs.removeObserver(onStartupFinished, "browser-delayed-startup-finished");
       /* Ignore the platform's online/offline status while running tests. */
       var ios = Components.classes["@mozilla.org/network/io-service;1"]
                 .getService(Components.interfaces.nsIIOService2);
       ios.manageOfflineStatus = false;
       ios.offline = false;
       Components.utils.import("resource://tps/tps.jsm");
       Components.utils.import("resource://tps/quit.js", TPS);
-      TPS.RunTestPhase(uri, phase, logfile, options);
+      TPS.RunTestPhase(uri, phase, logfile, options).catch(err => TPS.DumpError("TestPhase failed", err));
     };
     Services.obs.addObserver(onStartupFinished, "browser-delayed-startup-finished");
   },
 
   helpInfo: "  --tps <file>              Run TPS tests with the given test file.\n" +
             "  --tpsphase <phase>        Run the specified phase in the TPS test.\n" +
             "  --tpslogfile <file>       Logfile for TPS output.\n" +
             "  --ignore-unused-engines   Don't load engines not used in tests.\n",
--- a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
+++ b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
@@ -10,34 +10,33 @@ this.EXPORTED_SYMBOLS = [
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/FxAccounts.jsm");
 Cu.import("resource://gre/modules/FxAccountsClient.jsm");
 Cu.import("resource://gre/modules/FxAccountsConfig.jsm");
-Cu.import("resource://services-common/async.js");
 Cu.import("resource://services-sync/main.js");
 Cu.import("resource://tps/logger.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
 
 Cu.importGlobalProperties(["fetch"]);
 
 /**
  * Helper object for Firefox Accounts authentication
  */
 var Authentication = {
 
   /**
    * Check if an user has been logged in
    */
-  get isLoggedIn() {
-    return !!this.getSignedInUser();
+  async isLoggedIn() {
+    return !!(await this.getSignedInUser());
   },
 
   _getRestmailUsername(user) {
     const restmailSuffix = "@restmail.net";
     if (user.toLowerCase().endsWith(restmailSuffix)) {
       return user.slice(0, -restmailSuffix.length);
     }
     return null;
@@ -136,95 +135,76 @@ var Authentication = {
     return true;
   },
 
   /**
    * Wrapper to retrieve the currently signed in user
    *
    * @returns Information about the currently signed in user
    */
-  getSignedInUser: function getSignedInUser() {
-    let cb = Async.makeSpinningCallback();
-
-    fxAccounts.getSignedInUser().then(user => {
-      cb(null, user);
-    }, error => {
-      cb(error);
-    })
-
+  async getSignedInUser() {
     try {
-      return cb.wait();
+      return (await fxAccounts.getSignedInUser());
     } catch (error) {
       Logger.logError("getSignedInUser() failed with: " + JSON.stringify(error));
       throw error;
     }
   },
 
   /**
    * Wrapper to synchronize the login of a user
    *
    * @param account
    *        Account information of the user to login
    * @param account.username
    *        The username for the account (utf8)
    * @param account.password
    *        The user's password
    */
-  signIn: function signIn(account) {
-    let cb = Async.makeSpinningCallback();
-
+  async signIn(account) {
     Logger.AssertTrue(account.username, "Username has been found");
     Logger.AssertTrue(account.password, "Password has been found");
 
     Logger.logInfo("Login user: " + account.username);
 
-    // Required here since we don't go through the real login page
-    Async.promiseSpinningly(FxAccountsConfig.ensureConfigured());
+    try {
+      // Required here since we don't go through the real login page
+      await FxAccountsConfig.ensureConfigured();
 
-    let client = new FxAccountsClient();
-    client.signIn(account.username, account.password, true).then(credentials => {
-      return fxAccounts.setSignedInUser(credentials);
-    }).then(() => {
-      return this._completeVerification(account.username)
-    }).then(() => {
-      cb(null, true);
-    }, error => {
-      cb(error, false);
-    });
-
-    try {
-      cb.wait();
+      let client = new FxAccountsClient();
+      let credentials = await client.signIn(account.username, account.password, true)
+      await fxAccounts.setSignedInUser(credentials);
+      await this._completeVerification(account.username)
 
       if (Weave.Status.login !== Weave.LOGIN_SUCCEEDED) {
         Logger.logInfo("Logging into Weave.");
-        Async.promiseSpinningly(Weave.Service.login());
+        await Weave.Service.login();
         Logger.AssertEqual(Weave.Status.login, Weave.LOGIN_SUCCEEDED,
                            "Weave logged in");
       }
-
       return true;
     } catch (error) {
       throw new Error("signIn() failed with: " + error.message);
     }
   },
 
   /**
    * Sign out of Firefox Accounts. It also clears out the device ID, if we find one.
    */
-  signOut() {
-    if (Authentication.isLoggedIn) {
-      let user = Authentication.getSignedInUser();
+  async signOut() {
+    if (await Authentication.isLoggedIn()) {
+      let user = await Authentication.getSignedInUser();
       if (!user) {
         throw new Error("Failed to get signed in user!");
       }
       let fxc = new FxAccountsClient();
       let { sessionToken, deviceId } = user;
       if (deviceId) {
         Logger.logInfo("Destroying device " + deviceId);
-        Async.promiseSpinningly(fxAccounts.deleteDeviceRegistration(sessionToken, deviceId));
-        Async.promiseSpinningly(fxAccounts.signOut(true));
+        await fxAccounts.deleteDeviceRegistration(sessionToken, deviceId);
+        await fxAccounts.signOut(true);
       } else {
         Logger.logError("No device found.");
-        Async.promiseSpinningly(fxc.signOut(sessionToken, { service: "sync" }));
+        await fxc.signOut(sessionToken, { service: "sync" });
       }
     }
   }
 };
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -17,16 +17,17 @@ var module = this;
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/PromiseUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/main.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/telemetry.js");
 Cu.import("resource://services-sync/bookmark_validator.js");
 Cu.import("resource://services-sync/engines/passwords.js");
 Cu.import("resource://services-sync/engines/forms.js");
@@ -195,17 +196,21 @@ var TPS = {
           this._syncActive = false;
 
           this.delayAutoSync();
 
           // If this is the first sync error, retry...
           if (this._syncErrors === 0) {
             Logger.logInfo("Sync error; retrying...");
             this._syncErrors++;
-            Utils.nextTick(this.RunNextTestAction, this);
+            Utils.nextTick(() => {
+              this.RunNextTestAction().catch(err => {
+                this.DumpError("RunNextTestActionFailed", err);
+              });
+            });
           } else {
             this._triggeredSync = false;
             this.DumpError("Sync error; aborting test");
             return;
           }
 
           break;
 
@@ -272,17 +277,19 @@ var TPS = {
     // the FinishAsyncOperation without a StartAsyncOperation is fine, and works
     // as if a StartAsyncOperation had been called.
     if (this._operations_pending) {
       this._operations_pending--;
     }
     if (!this.operations_pending) {
       this._currentAction++;
       Utils.nextTick(function() {
-        this.RunNextTestAction();
+        this.RunNextTestAction().catch(err => {
+          this.DumpError("RunNextTestActionFailed", err);
+        });
       }, this);
     }
   },
 
   quit: function TPS__quit() {
     this._requestedQuit = true;
     this.goQuitApplication();
   },
@@ -362,46 +369,46 @@ var TPS = {
           break;
         default:
           Logger.AssertTrue(false, "invalid action: " + action);
       }
     }
     Logger.logPass("executing action " + action.toUpperCase() + " on pref");
   },
 
-  HandleForms(data, action) {
+  async HandleForms(data, action) {
     this.shouldValidateForms = true;
     for (let datum of data) {
       Logger.logInfo("executing action " + action.toUpperCase() +
                      " on form entry " + JSON.stringify(datum));
       let formdata = new FormData(datum, this._usSinceEpoch);
       switch (action) {
         case ACTION_ADD:
-          Async.promiseSpinningly(formdata.Create());
+          await formdata.Create();
           break;
         case ACTION_DELETE:
-          Async.promiseSpinningly(formdata.Remove());
+          await formdata.Remove();
           break;
         case ACTION_VERIFY:
-          Logger.AssertTrue(Async.promiseSpinningly(formdata.Find()),
+          Logger.AssertTrue(await formdata.Find(),
                             "form data not found");
           break;
         case ACTION_VERIFY_NOT:
-          Logger.AssertTrue(!Async.promiseSpinningly(formdata.Find()),
+          Logger.AssertTrue(!await formdata.Find(),
                             "form data found, but it shouldn't be present");
           break;
         default:
           Logger.AssertTrue(false, "invalid action: " + action);
       }
     }
     Logger.logPass("executing action " + action.toUpperCase() +
                    " on formdata");
   },
 
-  HandleHistory(entries, action) {
+  async HandleHistory(entries, action) {
     try {
       for (let entry of entries) {
         Logger.logInfo("executing action " + action.toUpperCase() +
                        " on history entry " + JSON.stringify(entry));
         switch (action) {
           case ACTION_ADD:
             HistoryEntry.Add(entry, this._usSinceEpoch);
             break;
@@ -579,41 +586,41 @@ var TPS = {
       }, 2000, this, "postmozmilltest");
     }
   },
 
   MozmillSetTestListener: function TPS__MozmillSetTestListener(obj) {
     Logger.logInfo("mozmill setTest: " + obj.name);
   },
 
-  Cleanup() {
+  async Cleanup() {
     try {
-      this.WipeServer();
+      await this.WipeServer();
     } catch (ex) {
       Logger.logError("Failed to wipe server: " + Log.exceptionStr(ex));
     }
     try {
-      if (Authentication.isLoggedIn) {
+      if (await Authentication.isLoggedIn()) {
         // signout and wait for Sync to completely reset itself.
         Logger.logInfo("signing out");
-        let waiter = this.createEventWaiter("weave:service:start-over:finish");
-        Authentication.signOut();
-        waiter();
+        let waiter = this.promiseObserver("weave:service:start-over:finish");
+        await Authentication.signOut();
+        await waiter;
         Logger.logInfo("signout complete");
       }
-      Authentication.deleteEmail(this.config.fx_account.username);
+      await Authentication.deleteEmail(this.config.fx_account.username);
     } catch (e) {
       Logger.logError("Failed to sign out: " + Log.exceptionStr(e));
     }
   },
 
   /**
    * Use Sync's bookmark validation code to see if we've corrupted the tree.
    */
-  ValidateBookmarks() {
+  async ValidateBookmarks() {
 
     let getServerBookmarkState = async () => {
       let bookmarkEngine = Weave.Service.engineManager.get("bookmarks");
       let collection = bookmarkEngine.itemSource();
       let collectionKey = bookmarkEngine.service.collectionKeys.keyForCollection(bookmarkEngine.name);
       collection.full = true;
       let items = [];
       let resp = await collection.get();
@@ -623,25 +630,25 @@ var TPS = {
         record.decrypt(collectionKey);
         items.push(record.cleartext);
       }
       return items;
     };
     let serverRecordDumpStr;
     try {
       Logger.logInfo("About to perform bookmark validation");
-      let clientTree = Async.promiseSpinningly(PlacesUtils.promiseBookmarksTree("", {
+      let clientTree = await (PlacesUtils.promiseBookmarksTree("", {
         includeItemIds: true
       }));
-      let serverRecords = Async.promiseSpinningly(getServerBookmarkState());
+      let serverRecords = await getServerBookmarkState();
       // We can't wait until catch to stringify this, since at that point it will have cycles.
       serverRecordDumpStr = JSON.stringify(serverRecords);
 
       let validator = new BookmarkValidator();
-      let {problemData} = Async.promiseSpinningly(validator.compareServerWithClient(serverRecords, clientTree));
+      let {problemData} = await validator.compareServerWithClient(serverRecords, clientTree);
 
       for (let {name, count} of problemData.getSummary()) {
         // Exclude mobile showing up on the server hackily so that we don't
         // report it every time, see bug 1273234 and 1274394 for more information.
         if (name === "serverUnexpected" && problemData.serverUnexpected.indexOf("mobile") >= 0) {
           --count;
         }
         if (count) {
@@ -658,25 +665,25 @@ var TPS = {
       if (serverRecordDumpStr) {
         Logger.logInfo("Server bookmark records:\n" + serverRecordDumpStr + "\n");
       }
       this.DumpError("Bookmark validation failed", e);
     }
     Logger.logInfo("Bookmark validation finished");
   },
 
-  ValidateCollection(engineName, ValidatorType) {
+  async ValidateCollection(engineName, ValidatorType) {
     let serverRecordDumpStr;
     let clientRecordDumpStr;
     try {
       Logger.logInfo(`About to perform validation for "${engineName}"`);
       let engine = Weave.Service.engineManager.get(engineName);
       let validator = new ValidatorType(engine);
-      let serverRecords = Async.promiseSpinningly(validator.getServerItems(engine));
-      let clientRecords = Async.promiseSpinningly(validator.getClientItems());
+      let serverRecords = await validator.getServerItems(engine);
+      let clientRecords = await validator.getClientItems();
       try {
         // This substantially improves the logs for addons while not making a
         // substantial difference for the other two
         clientRecordDumpStr = JSON.stringify(clientRecords.map(r => {
           let res = validator.normalizeClientItem(r);
           delete res.original; // Try and prevent cyclic references
           return res;
         }));
@@ -685,17 +692,17 @@ var TPS = {
         clientRecordDumpStr = "<Cyclic value>";
       }
       try {
         serverRecordDumpStr = JSON.stringify(serverRecords);
       } catch (e) {
         // as above
         serverRecordDumpStr = "<Cyclic value>";
       }
-      let { problemData } = Async.promiseSpinningly(validator.compareClientWithServer(clientRecords, serverRecords));
+      let { problemData } = await validator.compareClientWithServer(clientRecords, serverRecords);
       for (let { name, count } of problemData.getSummary()) {
         if (count) {
           Logger.logInfo(`Validation problem: "${name}": ${JSON.stringify(problemData[name])}`);
         }
         Logger.AssertEqual(count, 0, `Validation error for "${engineName}" of type "${name}"`);
       }
     } catch (e) {
       // Dump the client records if possible
@@ -718,32 +725,31 @@ var TPS = {
   ValidateForms() {
     return this.ValidateCollection("forms", FormValidator);
   },
 
   ValidateAddons() {
     return this.ValidateCollection("addons", AddonValidator);
   },
 
-  RunNextTestAction() {
+  async RunNextTestAction() {
     try {
-      if (this._currentAction >=
-          this._phaselist[this._currentPhase].length) {
+      if (this._currentAction >= this._phaselist[this._currentPhase].length) {
         // Run necessary validations and then finish up
         if (this.shouldValidateBookmarks) {
-          this.ValidateBookmarks();
+          await this.ValidateBookmarks();
         }
         if (this.shouldValidatePasswords) {
-          this.ValidatePasswords();
+          await this.ValidatePasswords();
         }
         if (this.shouldValidateForms) {
-          this.ValidateForms();
+          await this.ValidateForms();
         }
         if (this.shouldValidateAddons) {
-          this.ValidateAddons();
+          await this.ValidateAddons();
         }
         // Force this early so that we run the validation and detect missing pings
         // *before* we start shutting down, since if we do it after, the python
         // code won't notice the failure.
         SyncTelemetry.shutdown();
         // we're all done
         Logger.logInfo("test phase " + this._currentPhase + ": " +
                        (this._errors ? "FAIL" : "PASS"));
@@ -757,17 +763,17 @@ var TPS = {
       else {
         this.DumpError("seconds-since-epoch not set");
         return;
       }
 
       let phase = this._phaselist[this._currentPhase];
       let action = phase[this._currentAction];
       Logger.logInfo("starting action: " + action[0].name);
-      action[0].apply(this, action.slice(1));
+      await action[0].apply(this, action.slice(1));
 
       // if we're in an async operation, don't continue on to the next action
       if (this._operations_pending)
         return;
 
       this._currentAction++;
     } catch (e) {
       if (Async.isShutdownException(e)) {
@@ -776,29 +782,29 @@ var TPS = {
         } else {
           this.DumpError("Sync aborted due to shutdown, but we didn't request it");
         }
       } else {
         this.DumpError("RunNextTestAction failed", e);
       }
       return;
     }
-    this.RunNextTestAction();
+    await this.RunNextTestAction();
   },
 
   _getFileRelativeToSourceRoot(testFileURL, relativePath) {
     let file = fileProtocolHandler.getFileFromURLSpec(testFileURL);
     let root = file // <root>/services/sync/tests/tps/test_foo.js
       .parent // <root>/services/sync/tests/tps
       .parent // <root>/services/sync/tests
       .parent // <root>/services/sync
       .parent // <root>/services
       .parent // <root>
       ;
-    root.appendRelativePath(relativePath);
+    root.appendRelativePath(OS.Path.normalize(relativePath));
     return root;
   },
 
   // Attempt to load the sync_ping_schema.json and initialize `this.pingValidator`
   // based on the source of the tps file. Assumes that it's at "../unit/sync_ping_schema.json"
   // relative to the directory the tps test file (testFile) is contained in.
   _tryLoadPingSchema(testFile) {
     try {
@@ -848,17 +854,17 @@ var TPS = {
    *         String URI of the file to open.
    * @param  phase
    *         String name of the phase to run.
    * @param  logpath
    *         String path of the log file to write to.
    * @param  options
    *         Object defining addition run-time options.
    */
-  RunTestPhase(file, phase, logpath, options) {
+  async RunTestPhase(file, phase, logpath, options) {
     try {
       let settings = options || {};
 
       Logger.init(logpath);
       Logger.logInfo("Sync version: " + WEAVE_VERSION);
       Logger.logInfo("Firefox buildid: " + Services.appinfo.appBuildID);
       Logger.logInfo("Firefox version: " + Services.appinfo.version);
       Logger.logInfo("Firefox source revision: " + (AppConstants.SOURCE_REVISION_URL || "unknown"));
@@ -876,28 +882,29 @@ var TPS = {
       }
 
       // We only want to do this if we modified the bookmarks this phase.
       this.shouldValidateBookmarks = false;
 
       // Always give Sync an extra tick to initialize. If we waited for the
       // service:ready event, this is required to ensure all handlers have
       // executed.
-      Utils.nextTick(this._executeTestPhase.bind(this, file, phase, settings));
+      await Async.promiseYield();
+      await this._executeTestPhase(file, phase, settings);
     } catch (e) {
       this.DumpError("RunTestPhase failed", e);
     }
   },
 
   /**
    * Executes a single test phase.
    *
    * This is called by RunTestPhase() after the environment is validated.
    */
-  _executeTestPhase: function _executeTestPhase(file, phase, settings) {
+  async _executeTestPhase(file, phase, settings) {
     try {
       this.config = JSON.parse(prefs.getCharPref("tps.config"));
       // parse the test file
       Services.scriptloader.loadSubScript(file, this);
       this._currentPhase = phase;
       // cleanup phases are in the format `cleanup-${profileName}`.
       if (this._currentPhase.startsWith("cleanup-")) {
         let profileToClean = this._currentPhase.slice("cleanup-".length);
@@ -937,19 +944,18 @@ var TPS = {
 
       Logger.logInfo("setting client.name to " + this.phases[this._currentPhase]);
       Weave.Svc.Prefs.set("client.name", this.phases[this._currentPhase]);
 
       this._interceptSyncTelemetry();
 
       // start processing the test actions
       this._currentAction = 0;
-      this._windowsUpDeferred.promise.then(() => {
-        this.RunNextTestAction();
-      });
+      await this._windowsUpDeferred.promise;
+      await this.RunNextTestAction();
     } catch (e) {
       this.DumpError("_executeTestPhase failed", e);
     }
   },
 
   /**
    * Override sync telemetry functions so that we can detect errors generating
    * the sync ping, and count how many pings we report.
@@ -1048,123 +1054,119 @@ var TPS = {
     Cu.import("resource://mozmill/modules/frame.js", frame);
     frame.events.addListener("setTest", this.MozmillSetTestListener.bind(this));
     frame.events.addListener("endTest", this.MozmillEndTestListener.bind(this));
     this.StartAsyncOperation();
     frame.runTestFile(mozmillfile.path, null);
   },
 
   /**
-   * Return an object that when called, will block until the named event
-   * is observed. This is similar to waitForEvent, although is typically safer
-   * if you need to do some other work that may make the event fire.
+   * Returns a promise that resolves when a specific observer notification is
+   * resolved. This is similar to the various waitFor* functions, although is
+   * typically safer if you need to do some other work that may make the event
+   * fire.
    *
    * eg:
    *    doSomething(); // causes the event to be fired.
-   *    waitForEvent("something");
+   *    await promiseObserver("something");
    * is risky as the call to doSomething may trigger the event before the
-   * waitForEvent call is made. Contrast with:
+   * promiseObserver call is made. Contrast with:
    *
-   *   let waiter = createEventWaiter("something"); // does *not* block.
+   *   let waiter = promiseObserver("something");
    *   doSomething(); // causes the event to be fired.
-   *   waiter(); // will return as soon as the event fires, even if it fires
-   *             // before this function is called.
+   *   await waiter;  // will return as soon as the event fires, even if it fires
+   *                  // before this function is called.
    *
    * @param aEventName
    *        String event to wait for.
    */
-  createEventWaiter(aEventName) {
-    Logger.logInfo("Setting up wait for " + aEventName + "...");
-    let cb = Async.makeSpinningCallback();
-    Svc.Obs.add(aEventName, cb);
-    return function() {
-      try {
-        cb.wait();
-      } finally {
-        Svc.Obs.remove(aEventName, cb);
-        Logger.logInfo(aEventName + " observed!");
+  promiseObserver(aEventName) {
+    return new Promise(resolve => {
+      Logger.logInfo("Setting up wait for " + aEventName + "...");
+      let handler = () => {
+        Logger.logInfo("Observed " + aEventName);
+        Svc.Obs.remove(aEventName, handler);
+        resolve();
       }
-    }
+      Svc.Obs.add(aEventName, handler);
+    });
   },
 
 
   /**
-   * Synchronously wait for the named event to be observed.
+   * Wait for the named event to be observed.
    *
-   * When the event is observed, the function will wait an extra tick before
-   * returning.
-   *
-   * Note that in general, you should probably use createEventWaiter unless you
+   * Note that in general, you should probably use promiseObserver unless you
    * are 100% sure that the event being waited on can only be sent after this
    * call adds the listener.
    *
    * @param aEventName
    *        String event to wait for.
    */
-  waitForEvent: function waitForEvent(aEventName) {
-    this.createEventWaiter(aEventName)();
+  async waitForEvent(aEventName) {
+    await this.promiseObserver(aEventName);
   },
 
   /**
    * Waits for Sync to logged in before returning
    */
-  waitForSetupComplete: function waitForSetup() {
+  async waitForSetupComplete() {
     if (!this._setupComplete) {
-      this.waitForEvent("weave:service:setup-complete");
+      await this.waitForEvent("weave:service:setup-complete");
     }
   },
 
   /**
    * Waits for Sync to be finished before returning
    */
-  waitForSyncFinished: function TPS__waitForSyncFinished() {
+  async waitForSyncFinished() {
     if (this._syncActive) {
-      this.waitForEvent("weave:service:resyncs-finished");
+      await this.waitForEvent("weave:service:resyncs-finished");
     }
   },
 
   /**
    * Waits for Sync to start tracking before returning.
    */
-  waitForTracking: function waitForTracking() {
+  async waitForTracking() {
     if (!this._isTracking) {
-      this.waitForEvent("weave:engine:start-tracking");
+      await this.waitForEvent("weave:engine:start-tracking");
     }
   },
 
   /**
    * Login on the server
    */
-  Login: function Login(force) {
-    if (Authentication.isLoggedIn && !force) {
+  async Login(force) {
+    if ((await Authentication.isLoggedIn()) && !force) {
       return;
     }
 
     // This might come during Authentication.signIn
     this._triggeredSync = true;
     Logger.logInfo("Setting client credentials and login.");
-    Authentication.signIn(this.config.fx_account);
-    this.waitForSetupComplete();
+    await Authentication.signIn(this.config.fx_account);
+    await this.waitForSetupComplete();
     Logger.AssertEqual(Weave.Status.service, Weave.STATUS_OK, "Weave status OK");
-    this.waitForTracking();
+    await this.waitForTracking();
     // We might get an initial sync at login time - let that complete.
-    this.waitForSyncFinished();
+    await this.waitForSyncFinished();
   },
 
   /**
    * Triggers a sync operation
    *
    * @param {String} [wipeAction]
    *        Type of wipe to perform (resetClient, wipeClient, wipeRemote)
    *
    */
-  Sync: function TPS__Sync(wipeAction) {
+  async Sync(wipeAction) {
     if (this._syncActive) {
       Logger.logInfo("WARNING: Sync currently active! Waiting, before triggering another");
-      this.waitForSyncFinished();
+      await this.waitForSyncFinished();
     }
     Logger.logInfo("Executing Sync" + (wipeAction ? ": " + wipeAction : ""));
 
     // Force a wipe action if requested. In case of an initial sync the pref
     // will be overwritten by Sync itself (see bug 992198), so ensure that we
     // also handle it via the "weave:service:setup-complete" notification.
     if (wipeAction) {
       this._syncWipeAction = wipeAction;
@@ -1173,34 +1175,34 @@ var TPS = {
       Weave.Svc.Prefs.reset("firstSync");
     }
 
     this.Login(false);
     ++this._syncCount;
 
     this._triggeredSync = true;
     this.StartAsyncOperation();
-    Async.promiseSpinningly(Weave.Service.sync());
+    await Weave.Service.sync();
     Logger.logInfo("Sync is complete");
   },
 
-  WipeServer: function TPS__WipeServer() {
+  async WipeServer() {
     Logger.logInfo("Wiping data from server.");
 
-    this.Login(false);
-    Async.promiseSpinningly(Weave.Service.login());
-    Async.promiseSpinningly(Weave.Service.wipeServer());
+    await this.Login(false);
+    await Weave.Service.login();
+    await Weave.Service.wipeServer();
   },
 
   /**
    * Action which ensures changes are being tracked before returning.
    */
-  EnsureTracking: function EnsureTracking() {
-    this.Login(false);
-    this.waitForTracking();
+  async EnsureTracking() {
+    await this.Login(false);
+    await this.waitForTracking();
   }
 };
 
 var Addons = {
   install: function Addons__install(addons) {
     TPS.HandleAddons(addons, ACTION_ADD);
   },
   setEnabled: function Addons__setEnabled(addons, state) {