"Fossies" - the Fresh Open Source Software Archive

Member "UXP-2019.06.08/services/sync/tps/extensions/tps/resource/tps.jsm" (8 Jun 2019, 32265 Bytes) of package /linux/www/UXP-2019.06.08.tar.gz:


As a special service "Fossies" has tried to format the requested text file into HTML format (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "tps.jsm": 2019.03.27_vs_2019.06.08.

    1 /* This Source Code Form is subject to the terms of the Mozilla Public
    2  * License, v. 2.0. If a copy of the MPL was not distributed with this
    3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
    4 
    5  /* This is a JavaScript module (JSM) to be imported via
    6   * Components.utils.import() and acts as a singleton. Only the following
    7   * listed symbols will exposed on import, and only when and where imported.
    8   */
    9 
   10 var EXPORTED_SYMBOLS = ["ACTIONS", "TPS"];
   11 
   12 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
   13 
   14 var module = this;
   15 
   16 // Global modules
   17 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
   18 Cu.import("resource://gre/modules/Services.jsm");
   19 Cu.import("resource://services-common/async.js");
   20 Cu.import("resource://services-sync/constants.js");
   21 Cu.import("resource://services-sync/main.js");
   22 Cu.import("resource://services-sync/util.js");
   23 
   24 // TPS modules
   25 Cu.import("resource://tps/logger.jsm");
   26 
   27 // Module wrappers for tests
   28 Cu.import("resource://tps/modules/addons.jsm");
   29 Cu.import("resource://tps/modules/bookmarks.jsm");
   30 Cu.import("resource://tps/modules/forms.jsm");
   31 Cu.import("resource://tps/modules/history.jsm");
   32 Cu.import("resource://tps/modules/passwords.jsm");
   33 Cu.import("resource://tps/modules/prefs.jsm");
   34 Cu.import("resource://tps/modules/tabs.jsm");
   35 Cu.import("resource://tps/modules/windows.jsm");
   36 
   37 var hh = Cc["@mozilla.org/network/protocol;1?name=http"]
   38          .getService(Ci.nsIHttpProtocolHandler);
   39 var prefs = Cc["@mozilla.org/preferences-service;1"]
   40             .getService(Ci.nsIPrefBranch);
   41 
   42 var mozmillInit = {};
   43 Cu.import('resource://mozmill/driver/mozmill.js', mozmillInit);
   44 
   45 // Options for wiping data during a sync
   46 const SYNC_RESET_CLIENT = "resetClient";
   47 const SYNC_WIPE_CLIENT  = "wipeClient";
   48 const SYNC_WIPE_REMOTE  = "wipeRemote";
   49 
   50 // Actions a test can perform
   51 const ACTION_ADD                = "add";
   52 const ACTION_DELETE             = "delete";
   53 const ACTION_MODIFY             = "modify";
   54 const ACTION_PRIVATE_BROWSING   = "private-browsing";
   55 const ACTION_SET_ENABLED        = "set-enabled";
   56 const ACTION_SYNC               = "sync";
   57 const ACTION_SYNC_RESET_CLIENT  = SYNC_RESET_CLIENT;
   58 const ACTION_SYNC_WIPE_CLIENT   = SYNC_WIPE_CLIENT;
   59 const ACTION_SYNC_WIPE_REMOTE   = SYNC_WIPE_REMOTE;
   60 const ACTION_VERIFY             = "verify";
   61 const ACTION_VERIFY_NOT         = "verify-not";
   62 
   63 const ACTIONS = [
   64   ACTION_ADD,
   65   ACTION_DELETE,
   66   ACTION_MODIFY,
   67   ACTION_PRIVATE_BROWSING,
   68   ACTION_SET_ENABLED,
   69   ACTION_SYNC,
   70   ACTION_SYNC_RESET_CLIENT,
   71   ACTION_SYNC_WIPE_CLIENT,
   72   ACTION_SYNC_WIPE_REMOTE,
   73   ACTION_VERIFY,
   74   ACTION_VERIFY_NOT,
   75 ];
   76 
   77 const OBSERVER_TOPICS = ["private-browsing",
   78                          "quit-application-requested",
   79                          "sessionstore-windows-restored",
   80                          "weave:engine:start-tracking",
   81                          "weave:engine:stop-tracking",
   82                          "weave:service:login:error",
   83                          "weave:service:setup-complete",
   84                          "weave:service:sync:finish",
   85                          "weave:service:sync:delayed",
   86                          "weave:service:sync:error",
   87                          "weave:service:sync:start"
   88                         ];
   89 
   90 var TPS = {
   91   _currentAction: -1,
   92   _currentPhase: -1,
   93   _enabledEngines: null,
   94   _errors: 0,
   95   _finalPhase: false,
   96   _isTracking: false,
   97   _operations_pending: 0,
   98   _phaseFinished: false,
   99   _phaselist: {},
  100   _setupComplete: false,
  101   _syncActive: false,
  102   _syncErrors: 0,
  103   _syncWipeAction: null,
  104   _tabsAdded: 0,
  105   _tabsFinished: 0,
  106   _test: null,
  107   _triggeredSync: false,
  108   _usSinceEpoch: 0,
  109 
  110   _init: function TPS__init() {
  111     // Check if Firefox Accounts is enabled
  112     let service = Cc["@mozilla.org/weave/service;1"]
  113                   .getService(Components.interfaces.nsISupports)
  114                   .wrappedJSObject;
  115 
  116     this.delayAutoSync();
  117 
  118     OBSERVER_TOPICS.forEach(function (aTopic) {
  119       Services.obs.addObserver(this, aTopic, true);
  120     }, this);
  121 
  122     // Import the appropriate authentication module
  123     Cu.import("resource://tps/auth/sync.jsm", module);
  124   },
  125 
  126   DumpError: function TPS__DumpError(msg) {
  127     this._errors++;
  128     Logger.logError("[phase" + this._currentPhase + "] " + msg);
  129     this.quit();
  130   },
  131 
  132   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
  133                                          Ci.nsISupportsWeakReference]),
  134 
  135   observe: function TPS__observe(subject, topic, data) {
  136     try {
  137       Logger.logInfo("----------event observed: " + topic);
  138 
  139       switch(topic) {
  140         case "private-browsing":
  141           Logger.logInfo("private browsing " + data);
  142           break;
  143 
  144         case "quit-application-requested":
  145           // Ensure that we eventually wipe the data on the server
  146           if (this._errors || !this._phaseFinished || this._finalPhase) {
  147             try {
  148               this.WipeServer();
  149             } catch (ex) {}
  150           }
  151 
  152           OBSERVER_TOPICS.forEach(function(topic) {
  153             Services.obs.removeObserver(this, topic);
  154           }, this);
  155 
  156           Logger.close();
  157 
  158           break;
  159 
  160         case "sessionstore-windows-restored":
  161           Utils.nextTick(this.RunNextTestAction, this);
  162           break;
  163 
  164         case "weave:service:setup-complete":
  165           this._setupComplete = true;
  166 
  167           if (this._syncWipeAction) {
  168             Weave.Svc.Prefs.set("firstSync", this._syncWipeAction);
  169             this._syncWipeAction = null;
  170           }
  171 
  172           break;
  173 
  174         case "weave:service:sync:error":
  175           this._syncActive = false;
  176 
  177           this.delayAutoSync();
  178 
  179           // If this is the first sync error, retry...
  180           if (this._syncErrors === 0) {
  181             Logger.logInfo("Sync error; retrying...");
  182             this._syncErrors++;
  183             Utils.nextTick(this.RunNextTestAction, this);
  184           }
  185           else {
  186             this._triggeredSync = false;
  187             this.DumpError("Sync error; aborting test");
  188             return;
  189           }
  190 
  191           break;
  192 
  193         case "weave:service:sync:finish":
  194           this._syncActive = false;
  195           this._syncErrors = 0;
  196           this._triggeredSync = false;
  197 
  198           this.delayAutoSync();
  199 
  200           // Wait a second before continuing, otherwise we can get
  201           // 'sync not complete' errors.
  202           Utils.namedTimer(function () {
  203             this.FinishAsyncOperation();
  204           }, 1000, this, "postsync");
  205 
  206           break;
  207 
  208         case "weave:service:sync:start":
  209           // Ensure that the sync operation has been started by TPS
  210           if (!this._triggeredSync) {
  211             this.DumpError("Automatic sync got triggered, which is not allowed.")
  212           }
  213 
  214           this._syncActive = true;
  215           break;
  216 
  217         case "weave:engine:start-tracking":
  218           this._isTracking = true;
  219           break;
  220 
  221         case "weave:engine:stop-tracking":
  222           this._isTracking = false;
  223           break;
  224       }
  225     }
  226     catch (e) {
  227       this.DumpError("Exception caught: " + Utils.exceptionStr(e));
  228       return;
  229     }
  230   },
  231 
  232   /**
  233    * Given that we cannot complely disable the automatic sync operations, we
  234    * massively delay the next sync. Sync operations have to only happen when
  235    * directly called via TPS.Sync()!
  236    */
  237   delayAutoSync: function TPS_delayAutoSync() {
  238     Weave.Svc.Prefs.set("scheduler.eolInterval", 7200);
  239     Weave.Svc.Prefs.set("scheduler.immediateInterval", 7200);
  240     Weave.Svc.Prefs.set("scheduler.idleInterval", 7200);
  241     Weave.Svc.Prefs.set("scheduler.activeInterval", 7200);
  242     Weave.Svc.Prefs.set("syncThreshold", 10000000);
  243   },
  244 
  245   StartAsyncOperation: function TPS__StartAsyncOperation() {
  246     this._operations_pending++;
  247   },
  248 
  249   FinishAsyncOperation: function TPS__FinishAsyncOperation() {
  250     this._operations_pending--;
  251     if (!this.operations_pending) {
  252       this._currentAction++;
  253       Utils.nextTick(function() {
  254         this.RunNextTestAction();
  255       }, this);
  256     }
  257   },
  258 
  259   quit: function TPS__quit() {
  260     this.goQuitApplication();
  261   },
  262 
  263   HandleWindows: function (aWindow, action) {
  264     Logger.logInfo("executing action " + action.toUpperCase() +
  265                    " on window " + JSON.stringify(aWindow));
  266     switch(action) {
  267       case ACTION_ADD:
  268         BrowserWindows.Add(aWindow.private, function(win) {
  269           Logger.logInfo("window finished loading");
  270           this.FinishAsyncOperation();
  271         }.bind(this));
  272         break;
  273     }
  274     Logger.logPass("executing action " + action.toUpperCase() + " on windows");
  275   },
  276 
  277   HandleTabs: function (tabs, action) {
  278     this._tabsAdded = tabs.length;
  279     this._tabsFinished = 0;
  280     for (let tab of tabs) {
  281       Logger.logInfo("executing action " + action.toUpperCase() +
  282                      " on tab " + JSON.stringify(tab));
  283       switch(action) {
  284         case ACTION_ADD:
  285           // When adding tabs, we keep track of how many tabs we're adding,
  286           // and wait until we've received that many onload events from our
  287           // new tabs before continuing
  288           let that = this;
  289           let taburi = tab.uri;
  290           BrowserTabs.Add(tab.uri, function() {
  291             that._tabsFinished++;
  292             Logger.logInfo("tab for " + taburi + " finished loading");
  293             if (that._tabsFinished == that._tabsAdded) {
  294               Logger.logInfo("all tabs loaded, continuing...");
  295 
  296               // Wait a second before continuing to be sure tabs can be synced,
  297               // otherwise we can get 'error locating tab'
  298               Utils.namedTimer(function () {
  299                 that.FinishAsyncOperation();
  300               }, 1000, this, "postTabsOpening");
  301             }
  302           });
  303           break;
  304         case ACTION_VERIFY:
  305           Logger.AssertTrue(typeof(tab.profile) != "undefined",
  306             "profile must be defined when verifying tabs");
  307           Logger.AssertTrue(
  308             BrowserTabs.Find(tab.uri, tab.title, tab.profile), "error locating tab");
  309           break;
  310         case ACTION_VERIFY_NOT:
  311           Logger.AssertTrue(typeof(tab.profile) != "undefined",
  312             "profile must be defined when verifying tabs");
  313           Logger.AssertTrue(
  314             !BrowserTabs.Find(tab.uri, tab.title, tab.profile),
  315             "tab found which was expected to be absent");
  316           break;
  317         default:
  318           Logger.AssertTrue(false, "invalid action: " + action);
  319       }
  320     }
  321     Logger.logPass("executing action " + action.toUpperCase() + " on tabs");
  322   },
  323 
  324   HandlePrefs: function (prefs, action) {
  325     for (let pref of prefs) {
  326       Logger.logInfo("executing action " + action.toUpperCase() +
  327                      " on pref " + JSON.stringify(pref));
  328       let preference = new Preference(pref);
  329       switch(action) {
  330         case ACTION_MODIFY:
  331           preference.Modify();
  332           break;
  333         case ACTION_VERIFY:
  334           preference.Find();
  335           break;
  336         default:
  337           Logger.AssertTrue(false, "invalid action: " + action);
  338       }
  339     }
  340     Logger.logPass("executing action " + action.toUpperCase() + " on pref");
  341   },
  342 
  343   HandleForms: function (data, action) {
  344     for (let datum of data) {
  345       Logger.logInfo("executing action " + action.toUpperCase() +
  346                      " on form entry " + JSON.stringify(datum));
  347       let formdata = new FormData(datum, this._usSinceEpoch);
  348       switch(action) {
  349         case ACTION_ADD:
  350           formdata.Create();
  351           break;
  352         case ACTION_DELETE:
  353           formdata.Remove();
  354           break;
  355         case ACTION_VERIFY:
  356           Logger.AssertTrue(formdata.Find(), "form data not found");
  357           break;
  358         case ACTION_VERIFY_NOT:
  359           Logger.AssertTrue(!formdata.Find(),
  360             "form data found, but it shouldn't be present");
  361           break;
  362         default:
  363           Logger.AssertTrue(false, "invalid action: " + action);
  364       }
  365     }
  366     Logger.logPass("executing action " + action.toUpperCase() +
  367                    " on formdata");
  368   },
  369 
  370   HandleHistory: function (entries, action) {
  371     try {
  372       for (let entry of entries) {
  373         Logger.logInfo("executing action " + action.toUpperCase() +
  374                        " on history entry " + JSON.stringify(entry));
  375         switch(action) {
  376           case ACTION_ADD:
  377             HistoryEntry.Add(entry, this._usSinceEpoch);
  378             break;
  379           case ACTION_DELETE:
  380             HistoryEntry.Delete(entry, this._usSinceEpoch);
  381             break;
  382           case ACTION_VERIFY:
  383             Logger.AssertTrue(HistoryEntry.Find(entry, this._usSinceEpoch),
  384               "Uri visits not found in history database");
  385             break;
  386           case ACTION_VERIFY_NOT:
  387             Logger.AssertTrue(!HistoryEntry.Find(entry, this._usSinceEpoch),
  388               "Uri visits found in history database, but they shouldn't be");
  389             break;
  390           default:
  391             Logger.AssertTrue(false, "invalid action: " + action);
  392         }
  393       }
  394       Logger.logPass("executing action " + action.toUpperCase() +
  395                      " on history");
  396     }
  397     catch(e) {
  398       DumpHistory();
  399       throw(e);
  400     }
  401   },
  402 
  403   HandlePasswords: function (passwords, action) {
  404     try {
  405       for (let password of passwords) {
  406         let password_id = -1;
  407         Logger.logInfo("executing action " + action.toUpperCase() +
  408                       " on password " + JSON.stringify(password));
  409         var password = new Password(password);
  410         switch (action) {
  411           case ACTION_ADD:
  412             Logger.AssertTrue(password.Create() > -1, "error adding password");
  413             break;
  414           case ACTION_VERIFY:
  415             Logger.AssertTrue(password.Find() != -1, "password not found");
  416             break;
  417           case ACTION_VERIFY_NOT:
  418             Logger.AssertTrue(password.Find() == -1,
  419               "password found, but it shouldn't exist");
  420             break;
  421           case ACTION_DELETE:
  422             Logger.AssertTrue(password.Find() != -1, "password not found");
  423             password.Remove();
  424             break;
  425           case ACTION_MODIFY:
  426             if (password.updateProps != null) {
  427               Logger.AssertTrue(password.Find() != -1, "password not found");
  428               password.Update();
  429             }
  430             break;
  431           default:
  432             Logger.AssertTrue(false, "invalid action: " + action);
  433         }
  434       }
  435       Logger.logPass("executing action " + action.toUpperCase() +
  436                      " on passwords");
  437     }
  438     catch(e) {
  439       DumpPasswords();
  440       throw(e);
  441     }
  442   },
  443 
  444   HandleAddons: function (addons, action, state) {
  445     for (let entry of addons) {
  446       Logger.logInfo("executing action " + action.toUpperCase() +
  447                      " on addon " + JSON.stringify(entry));
  448       let addon = new Addon(this, entry);
  449       switch(action) {
  450         case ACTION_ADD:
  451           addon.install();
  452           break;
  453         case ACTION_DELETE:
  454           addon.uninstall();
  455           break;
  456         case ACTION_VERIFY:
  457           Logger.AssertTrue(addon.find(state), 'addon ' + addon.id + ' not found');
  458           break;
  459         case ACTION_VERIFY_NOT:
  460           Logger.AssertFalse(addon.find(state), 'addon ' + addon.id + " is present, but it shouldn't be");
  461           break;
  462         case ACTION_SET_ENABLED:
  463           Logger.AssertTrue(addon.setEnabled(state), 'addon ' + addon.id + ' not found');
  464           break;
  465         default:
  466           throw new Error("Unknown action for add-on: " + action);
  467       }
  468     }
  469     Logger.logPass("executing action " + action.toUpperCase() +
  470                    " on addons");
  471   },
  472 
  473   HandleBookmarks: function (bookmarks, action) {
  474     try {
  475       let items = [];
  476       for (folder in bookmarks) {
  477         let last_item_pos = -1;
  478         for (let bookmark of bookmarks[folder]) {
  479           Logger.clearPotentialError();
  480           let placesItem;
  481           bookmark['location'] = folder;
  482 
  483           if (last_item_pos != -1)
  484             bookmark['last_item_pos'] = last_item_pos;
  485           let item_id = -1;
  486 
  487           if (action != ACTION_MODIFY && action != ACTION_DELETE)
  488             Logger.logInfo("executing action " + action.toUpperCase() +
  489                            " on bookmark " + JSON.stringify(bookmark));
  490 
  491           if ("uri" in bookmark)
  492             placesItem = new Bookmark(bookmark);
  493           else if ("folder" in bookmark)
  494             placesItem = new BookmarkFolder(bookmark);
  495           else if ("livemark" in bookmark)
  496             placesItem = new Livemark(bookmark);
  497           else if ("separator" in bookmark)
  498             placesItem = new Separator(bookmark);
  499 
  500           if (action == ACTION_ADD) {
  501             item_id = placesItem.Create();
  502           }
  503           else {
  504             item_id = placesItem.Find();
  505             if (action == ACTION_VERIFY_NOT) {
  506               Logger.AssertTrue(item_id == -1,
  507                 "places item exists but it shouldn't: " +
  508                 JSON.stringify(bookmark));
  509             }
  510             else
  511               Logger.AssertTrue(item_id != -1, "places item not found", true);
  512           }
  513 
  514           last_item_pos = placesItem.GetItemIndex();
  515           items.push(placesItem);
  516         }
  517       }
  518 
  519       if (action == ACTION_DELETE || action == ACTION_MODIFY) {
  520         for (let item of items) {
  521           Logger.logInfo("executing action " + action.toUpperCase() +
  522                          " on bookmark " + JSON.stringify(item));
  523           switch(action) {
  524             case ACTION_DELETE:
  525               item.Remove();
  526               break;
  527             case ACTION_MODIFY:
  528               if (item.updateProps != null)
  529                 item.Update();
  530               break;
  531           }
  532         }
  533       }
  534 
  535       Logger.logPass("executing action " + action.toUpperCase() +
  536         " on bookmarks");
  537     }
  538     catch (e) {
  539       DumpBookmarks();
  540       throw(e);
  541     }
  542   },
  543 
  544   MozmillEndTestListener: function TPS__MozmillEndTestListener(obj) {
  545     Logger.logInfo("mozmill endTest: " + JSON.stringify(obj));
  546     if (obj.failed > 0) {
  547       this.DumpError('mozmill test failed, name: ' + obj.name + ', reason: ' + JSON.stringify(obj.fails));
  548       return;
  549     }
  550     else if ('skipped' in obj && obj.skipped) {
  551       this.DumpError('mozmill test failed, name: ' + obj.name + ', reason: ' + obj.skipped_reason);
  552       return;
  553     }
  554     else {
  555       Utils.namedTimer(function() {
  556         this.FinishAsyncOperation();
  557       }, 2000, this, "postmozmilltest");
  558     }
  559   },
  560 
  561   MozmillSetTestListener: function TPS__MozmillSetTestListener(obj) {
  562     Logger.logInfo("mozmill setTest: " + obj.name);
  563   },
  564 
  565   RunNextTestAction: function() {
  566     try {
  567       if (this._currentAction >=
  568           this._phaselist["phase" + this._currentPhase].length) {
  569         // we're all done
  570         Logger.logInfo("test phase " + this._currentPhase + ": " +
  571                        (this._errors ? "FAIL" : "PASS"));
  572         this._phaseFinished = true;
  573         this.quit();
  574         return;
  575       }
  576 
  577       if (this.seconds_since_epoch)
  578         this._usSinceEpoch = this.seconds_since_epoch * 1000 * 1000;
  579       else {
  580         this.DumpError("seconds-since-epoch not set");
  581         return;
  582       }
  583 
  584       let phase = this._phaselist["phase" + this._currentPhase];
  585       let action = phase[this._currentAction];
  586       Logger.logInfo("starting action: " + action[0].name);
  587       action[0].apply(this, action.slice(1));
  588 
  589       // if we're in an async operation, don't continue on to the next action
  590       if (this._operations_pending)
  591         return;
  592 
  593       this._currentAction++;
  594     }
  595     catch(e) {
  596       this.DumpError("Exception caught: " + Utils.exceptionStr(e));
  597       return;
  598     }
  599     this.RunNextTestAction();
  600   },
  601 
  602   /**
  603    * Runs a single test phase.
  604    *
  605    * This is the main entry point for each phase of a test. The TPS command
  606    * line driver loads this module and calls into the function with the
  607    * arguments from the command line.
  608    *
  609    * When a phase is executed, the file is loaded as JavaScript into the
  610    * current object.
  611    *
  612    * The following keys in the options argument have meaning:
  613    *
  614    *   - ignoreUnusedEngines  If true, unused engines will be unloaded from
  615    *                          Sync. This makes output easier to parse and is
  616    *                          useful for debugging test failures.
  617    *
  618    * @param  file
  619    *         String URI of the file to open.
  620    * @param  phase
  621    *         String name of the phase to run.
  622    * @param  logpath
  623    *         String path of the log file to write to.
  624    * @param  options
  625    *         Object defining addition run-time options.
  626    */
  627   RunTestPhase: function (file, phase, logpath, options) {
  628     try {
  629       let settings = options || {};
  630 
  631       Logger.init(logpath);
  632       Logger.logInfo("Sync version: " + WEAVE_VERSION);
  633       Logger.logInfo(Services.appinfo.name + " buildid: " + Services.appinfo.appBuildID);
  634       Logger.logInfo(Services.appinfo.name + " version: " + Services.appinfo.version);
  635 
  636       // do some sync housekeeping
  637       if (Weave.Service.isLoggedIn) {
  638         this.DumpError("Sync logged in on startup...profile may be dirty");
  639         return;
  640       }
  641 
  642       // Wait for Sync service to become ready.
  643       if (!Weave.Status.ready) {
  644         this.waitForEvent("weave:service:ready");
  645       }
  646 
  647       // Always give Sync an extra tick to initialize. If we waited for the
  648       // service:ready event, this is required to ensure all handlers have
  649       // executed.
  650       Utils.nextTick(this._executeTestPhase.bind(this, file, phase, settings));
  651     } catch(e) {
  652       this.DumpError("Exception caught: " + Utils.exceptionStr(e));
  653       return;
  654     }
  655   },
  656 
  657   /**
  658    * Executes a single test phase.
  659    *
  660    * This is called by RunTestPhase() after the environment is validated.
  661    */
  662   _executeTestPhase: function _executeTestPhase(file, phase, settings) {
  663     try {
  664       // parse the test file
  665       Services.scriptloader.loadSubScript(file, this);
  666       this._currentPhase = phase;
  667       let this_phase = this._phaselist["phase" + this._currentPhase];
  668 
  669       if (this_phase == undefined) {
  670         this.DumpError("invalid phase " + this._currentPhase);
  671         return;
  672       }
  673 
  674       if (this.phases["phase" + this._currentPhase] == undefined) {
  675         this.DumpError("no profile defined for phase " + this._currentPhase);
  676         return;
  677       }
  678 
  679       // If we have restricted the active engines, unregister engines we don't
  680       // care about.
  681       if (settings.ignoreUnusedEngines && Array.isArray(this._enabledEngines)) {
  682         let names = {};
  683         for (let name of this._enabledEngines) {
  684           names[name] = true;
  685         }
  686 
  687         for (let engine of Weave.Service.engineManager.getEnabled()) {
  688           if (!(engine.name in names)) {
  689             Logger.logInfo("Unregistering unused engine: " + engine.name);
  690             Weave.Service.engineManager.unregister(engine);
  691           }
  692         }
  693       }
  694 
  695       Logger.logInfo("Starting phase " + parseInt(phase, 10) + "/" +
  696                      Object.keys(this._phaselist).length);
  697 
  698       Logger.logInfo("setting client.name to " + this.phases["phase" + this._currentPhase]);
  699       Weave.Svc.Prefs.set("client.name", this.phases["phase" + this._currentPhase]);
  700 
  701       // TODO Phases should be defined in a data type that has strong
  702       // ordering, not by lexical sorting.
  703       let currentPhase = parseInt(this._currentPhase, 10);
  704 
  705       // Login at the beginning of the test.
  706       if (currentPhase <= 1) {
  707         this_phase.unshift([this.Login]);
  708       }
  709 
  710       // Wipe the server at the end of the final test phase.
  711       if (currentPhase >= Object.keys(this.phases).length) {
  712         this._finalPhase = true;
  713       }
  714 
  715       // If a custom server was specified, set it now
  716       if (this.config["serverURL"]) {
  717         Weave.Service.serverURL = this.config.serverURL;
  718         prefs.setCharPref('tps.serverURL', this.config.serverURL);
  719       }
  720 
  721       // Store account details as prefs so they're accessible to the Mozmill
  722       // framework.
  723       prefs.setCharPref('tps.account.username', this.config.sync_account.username);
  724       prefs.setCharPref('tps.account.password', this.config.sync_account.password);
  725       prefs.setCharPref('tps.account.passphrase', this.config.sync_account.passphrase);
  726 
  727       // start processing the test actions
  728       this._currentAction = 0;
  729     }
  730     catch(e) {
  731       this.DumpError("Exception caught: " + Utils.exceptionStr(e));
  732       return;
  733     }
  734   },
  735 
  736   /**
  737    * Register a single phase with the test harness.
  738    *
  739    * This is called when loading individual test files.
  740    *
  741    * @param  phasename
  742    *         String name of the phase being loaded.
  743    * @param  fnlist
  744    *         Array of functions/actions to perform.
  745    */
  746   Phase: function Test__Phase(phasename, fnlist) {
  747     this._phaselist[phasename] = fnlist;
  748   },
  749 
  750   /**
  751    * Restrict enabled Sync engines to a specified set.
  752    *
  753    * This can be called by a test to limit what engines are enabled. It is
  754    * recommended to call it to reduce the overhead and log clutter for the
  755    * test.
  756    *
  757    * The "clients" engine is special and is always enabled, so there is no
  758    * need to specify it.
  759    *
  760    * @param  names
  761    *         Array of Strings for engines to make active during the test.
  762    */
  763   EnableEngines: function EnableEngines(names) {
  764     if (!Array.isArray(names)) {
  765       throw new Error("Argument to RestrictEngines() is not an array: "
  766                       + typeof(names));
  767     }
  768 
  769     this._enabledEngines = names;
  770   },
  771 
  772   RunMozmillTest: function TPS__RunMozmillTest(testfile) {
  773     var mozmillfile = Cc["@mozilla.org/file/local;1"]
  774                       .createInstance(Ci.nsILocalFile);
  775     if (hh.oscpu.toLowerCase().indexOf('windows') > -1) {
  776       let re = /\/(\w)\/(.*)/;
  777       this.config.testdir = this.config.testdir.replace(re, "$1://$2").replace(/\//g, "\\");
  778     }
  779     mozmillfile.initWithPath(this.config.testdir);
  780     mozmillfile.appendRelativePath(testfile);
  781     Logger.logInfo("Running mozmill test " + mozmillfile.path);
  782 
  783     var frame = {};
  784     Cu.import('resource://mozmill/modules/frame.js', frame);
  785     frame.events.addListener('setTest', this.MozmillSetTestListener.bind(this));
  786     frame.events.addListener('endTest', this.MozmillEndTestListener.bind(this));
  787     this.StartAsyncOperation();
  788     frame.runTestFile(mozmillfile.path, null);
  789   },
  790 
  791   /**
  792    * Synchronously wait for the named event to be observed.
  793    *
  794    * When the event is observed, the function will wait an extra tick before
  795    * returning.
  796    *
  797    * @param aEventName
  798    *        String event to wait for.
  799    */
  800   waitForEvent: function waitForEvent(aEventName) {
  801     Logger.logInfo("Waiting for " + aEventName + "...");
  802     let cb = Async.makeSpinningCallback();
  803     Svc.Obs.add(aEventName, cb);
  804     cb.wait();
  805     Svc.Obs.remove(aEventName, cb);
  806     Logger.logInfo(aEventName + " observed!");
  807 
  808     cb = Async.makeSpinningCallback();
  809     Utils.nextTick(cb);
  810     cb.wait();
  811   },
  812 
  813 
  814   /**
  815    * Waits for Sync to logged in before returning
  816    */
  817   waitForSetupComplete: function waitForSetup() {
  818     if (!this._setupComplete) {
  819       this.waitForEvent("weave:service:setup-complete");
  820     }
  821   },
  822 
  823   /**
  824    * Waits for Sync to be finished before returning
  825    */
  826   waitForSyncFinished: function TPS__waitForSyncFinished() {
  827     if (this._syncActive) {
  828       this.waitForEvent("weave:service:sync:finished");
  829     }
  830   },
  831 
  832   /**
  833    * Waits for Sync to start tracking before returning.
  834    */
  835   waitForTracking: function waitForTracking() {
  836     if (!this._isTracking) {
  837       this.waitForEvent("weave:engine:start-tracking");
  838     }
  839   },
  840 
  841   /**
  842    * Login on the server
  843    */
  844   Login: function Login(force) {
  845     if (Authentication.isLoggedIn && !force) {
  846       return;
  847     }
  848 
  849     Logger.logInfo("Setting client credentials and login.");
  850     let account = this.config.sync_account;
  851     Authentication.signIn(account);
  852     this.waitForSetupComplete();
  853     Logger.AssertEqual(Weave.Status.service, Weave.STATUS_OK, "Weave status OK");
  854     this.waitForTracking();
  855   },
  856 
  857   /**
  858    * Triggers a sync operation
  859    *
  860    * @param {String} [wipeAction]
  861    *        Type of wipe to perform (resetClient, wipeClient, wipeRemote)
  862    *
  863    */
  864   Sync: function TPS__Sync(wipeAction) {
  865     Logger.logInfo("Executing Sync" + (wipeAction ? ": " + wipeAction : ""));
  866 
  867     // Force a wipe action if requested. In case of an initial sync the pref
  868     // will be overwritten by Sync itself (see bug 992198), so ensure that we
  869     // also handle it via the "weave:service:setup-complete" notification.
  870     if (wipeAction) {
  871       this._syncWipeAction = wipeAction;
  872       Weave.Svc.Prefs.set("firstSync", wipeAction);
  873     }
  874     else {
  875       Weave.Svc.Prefs.reset("firstSync");
  876     }
  877 
  878     this.Login(false);
  879 
  880     this._triggeredSync = true;
  881     this.StartAsyncOperation();
  882     Weave.Service.sync();
  883   },
  884 
  885   WipeServer: function TPS__WipeServer() {
  886     Logger.logInfo("Wiping data from server.");
  887 
  888     this.Login(false);
  889     Weave.Service.login();
  890     Weave.Service.wipeServer();
  891   },
  892 
  893   /**
  894    * Action which ensures changes are being tracked before returning.
  895    */
  896   EnsureTracking: function EnsureTracking() {
  897     this.Login(false);
  898     this.waitForTracking();
  899   }
  900 };
  901 
  902 var Addons = {
  903   install: function Addons__install(addons) {
  904     TPS.HandleAddons(addons, ACTION_ADD);
  905   },
  906   setEnabled: function Addons__setEnabled(addons, state) {
  907     TPS.HandleAddons(addons, ACTION_SET_ENABLED, state);
  908   },
  909   uninstall: function Addons__uninstall(addons) {
  910     TPS.HandleAddons(addons, ACTION_DELETE);
  911   },
  912   verify: function Addons__verify(addons, state) {
  913     TPS.HandleAddons(addons, ACTION_VERIFY, state);
  914   },
  915   verifyNot: function Addons__verifyNot(addons) {
  916     TPS.HandleAddons(addons, ACTION_VERIFY_NOT);
  917   },
  918 };
  919 
  920 var Bookmarks = {
  921   add: function Bookmarks__add(bookmarks) {
  922     TPS.HandleBookmarks(bookmarks, ACTION_ADD);
  923   },
  924   modify: function Bookmarks__modify(bookmarks) {
  925     TPS.HandleBookmarks(bookmarks, ACTION_MODIFY);
  926   },
  927   delete: function Bookmarks__delete(bookmarks) {
  928     TPS.HandleBookmarks(bookmarks, ACTION_DELETE);
  929   },
  930   verify: function Bookmarks__verify(bookmarks) {
  931     TPS.HandleBookmarks(bookmarks, ACTION_VERIFY);
  932   },
  933   verifyNot: function Bookmarks__verifyNot(bookmarks) {
  934     TPS.HandleBookmarks(bookmarks, ACTION_VERIFY_NOT);
  935   }
  936 };
  937 
  938 var Formdata = {
  939   add: function Formdata__add(formdata) {
  940     this.HandleForms(formdata, ACTION_ADD);
  941   },
  942   delete: function Formdata__delete(formdata) {
  943     this.HandleForms(formdata, ACTION_DELETE);
  944   },
  945   verify: function Formdata__verify(formdata) {
  946     this.HandleForms(formdata, ACTION_VERIFY);
  947   },
  948   verifyNot: function Formdata__verifyNot(formdata) {
  949     this.HandleForms(formdata, ACTION_VERIFY_NOT);
  950   }
  951 };
  952 
  953 var History = {
  954   add: function History__add(history) {
  955     this.HandleHistory(history, ACTION_ADD);
  956   },
  957   delete: function History__delete(history) {
  958     this.HandleHistory(history, ACTION_DELETE);
  959   },
  960   verify: function History__verify(history) {
  961     this.HandleHistory(history, ACTION_VERIFY);
  962   },
  963   verifyNot: function History__verifyNot(history) {
  964     this.HandleHistory(history, ACTION_VERIFY_NOT);
  965   }
  966 };
  967 
  968 var Passwords = {
  969   add: function Passwords__add(passwords) {
  970     this.HandlePasswords(passwords, ACTION_ADD);
  971   },
  972   modify: function Passwords__modify(passwords) {
  973     this.HandlePasswords(passwords, ACTION_MODIFY);
  974   },
  975   delete: function Passwords__delete(passwords) {
  976     this.HandlePasswords(passwords, ACTION_DELETE);
  977   },
  978   verify: function Passwords__verify(passwords) {
  979     this.HandlePasswords(passwords, ACTION_VERIFY);
  980   },
  981   verifyNot: function Passwords__verifyNot(passwords) {
  982     this.HandlePasswords(passwords, ACTION_VERIFY_NOT);
  983   }
  984 };
  985 
  986 var Prefs = {
  987   modify: function Prefs__modify(prefs) {
  988     TPS.HandlePrefs(prefs, ACTION_MODIFY);
  989   },
  990   verify: function Prefs__verify(prefs) {
  991     TPS.HandlePrefs(prefs, ACTION_VERIFY);
  992   }
  993 };
  994 
  995 var Tabs = {
  996   add: function Tabs__add(tabs) {
  997     TPS.StartAsyncOperation();
  998     TPS.HandleTabs(tabs, ACTION_ADD);
  999   },
 1000   verify: function Tabs__verify(tabs) {
 1001     TPS.HandleTabs(tabs, ACTION_VERIFY);
 1002   },
 1003   verifyNot: function Tabs__verifyNot(tabs) {
 1004     TPS.HandleTabs(tabs, ACTION_VERIFY_NOT);
 1005   }
 1006 };
 1007 
 1008 var Windows = {
 1009   add: function Window__add(aWindow) {
 1010     TPS.StartAsyncOperation();
 1011     TPS.HandleWindows(aWindow, ACTION_ADD);
 1012   },
 1013 };
 1014 
 1015 // Initialize TPS
 1016 TPS._init();