Backed out changeset 047060a5b1dc (bug 1078309) for failing browser_LoopContacts.js
authorJared Wein <jwein@mozilla.com>
Wed, 08 Oct 2014 23:44:10 -0400
changeset 225594 54bbede745bc85db1528c0e89a81b0c51faceeb6
parent 225593 cb36ba6e23e750f05d7b534197f254b43efc81a3
child 225595 f0b953a9d3a56e256678c618795e63b7a1246d5a
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1078309
milestone34.0a2
backs out047060a5b1dc0a266e39ba3a9694e5e1417a60e8
Backed out changeset 047060a5b1dc (bug 1078309) for failing browser_LoopContacts.js
browser/components/loop/LoopStorage.jsm
browser/components/loop/MozLoopService.jsm
browser/components/loop/content/js/contacts.js
browser/components/loop/content/js/contacts.jsx
browser/components/loop/content/js/panel.js
browser/components/loop/content/js/panel.jsx
browser/components/loop/test/mochitest/browser_LoopContacts.js
--- a/browser/components/loop/LoopStorage.jsm
+++ b/browser/components/loop/LoopStorage.jsm
@@ -20,19 +20,17 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
   const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
   return new EventEmitter();
 });
 
 this.EXPORTED_SYMBOLS = ["LoopStorage"];
 
-const kDatabasePrefix = "loop-";
-const kDefaultDatabaseName = "default";
-let gDatabaseName = kDatabasePrefix + kDefaultDatabaseName;
+const kDatabaseName = "loop";
 const kDatabaseVersion = 1;
 
 let gWaitForOpenCallbacks = new Set();
 let gDatabase = null;
 let gClosed = false;
 
 /**
  * Properly shut the database instance down. This is done on application shutdown.
@@ -80,26 +78,26 @@ const ensureDatabaseOpen = function(onOp
 
   let invokeCallbacks = err => {
     for (let callback of gWaitForOpenCallbacks) {
       callback(err, gDatabase);
     }
     gWaitForOpenCallbacks.clear();
   };
 
-  let openRequest = indexedDB.open(gDatabaseName, kDatabaseVersion);
+  let openRequest = indexedDB.open(kDatabaseName, kDatabaseVersion);
 
   openRequest.onblocked = function(event) {
     invokeCallbacks(new Error("Database cannot be upgraded cause in use: " + event.target.error));
   };
 
   openRequest.onerror = function(event) {
     // Try to delete the old database so that we can start this process over
     // next time.
-    indexedDB.deleteDatabase(gDatabaseName);
+    indexedDB.deleteDatabase(kDatabaseName);
     invokeCallbacks(new Error("Error while opening database: " + event.target.errorCode));
   };
 
   openRequest.onupgradeneeded = function(event) {
     let db = event.target.result;
     eventEmitter.emit("upgrade", db, event.oldVersion, kDatabaseVersion);
   };
 
@@ -107,39 +105,16 @@ const ensureDatabaseOpen = function(onOp
     gDatabase = event.target.result;
     invokeCallbacks();
     // Close the database instance properly on application shutdown.
     Services.obs.addObserver(closeDatabase, "quit-application", false);
   };
 };
 
 /**
- * Switch to a database with a different name by closing the current connection
- * and making sure that the next connection attempt will be made using the updated
- * name.
- *
- * @param {String} name New name of the database to switch to.
- */
-const switchDatabase = function(name) {
-  if (name == gDatabaseName) {
-    // This is already the current database, so there's no need to switch.
-    return;
-  }
-
-  gDatabaseName = name;
-  if (gDatabase) {
-    try {
-      gDatabase.close();
-    } finally {
-      gDatabase = null;
-    }
-  }
-};
-
-/**
  * Start a transaction on the loop database and return it.
  *
  * @param {String}   store    Name of the object store to start a transaction on
  * @param {Function} callback Callback to be invoked once a database connection
  *                            is established and a transaction can be started.
  *                            It takes an Error object as first argument and the
  *                            transaction object as second argument.
  * @param {String}   mode     Mode of the transaction. May be 'readonly' or 'readwrite'
@@ -200,45 +175,28 @@ const getStore = function(store, callbac
  *
  * LoopStorage implements the EventEmitter interface by exposing two methods, `on`
  * and `off`, to subscribe to events.
  * At this point only the `upgrade` event will be emitted. This happens when the
  * database is loaded in memory and consumers will be able to change its structure.
  */
 this.LoopStorage = Object.freeze({
   /**
-   * @var {String} databaseName The name of the database that is currently active.
-   */
-  get databaseName() {
-    return gDatabaseName;
-  },
-
-  /**
    * Open a connection to the IndexedDB database and return the database object.
    *
    * @param {Function} callback Callback to be invoked once a database connection
    *                            is established. It takes an Error object as first
    *                            argument and the database connection object as
    *                            second argument, if successful.
    */
   getSingleton: function(callback) {
     ensureDatabaseOpen(callback);
   },
 
   /**
-   * Switch to a database with a different name.
-   *
-   * @param {String} name New name of the database to switch to. Defaults to
-   *                      `kDefaultDatabaseName`
-   */
-  switchDatabase: function(name = kDefaultDatabaseName) {
-    switchDatabase(name);
-  },
-
-  /**
    * Start a transaction on the loop database and return it.
    * If only two arguments are passed, the default mode will be assumed and the
    * second argument is assumed to be a callback.
    *
    * @param {String}   store    Name of the object store to start a transaction on
    * @param {Function} callback Callback to be invoked once a database connection
    *                            is established and a transaction can be started.
    *                            It takes an Error object as first argument and the
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -51,19 +51,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/FxAccountsProfileClient.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "HawkClient",
                                   "resource://services-common/hawkclient.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "deriveHawkCredentials",
                                   "resource://services-common/hawkrequest.js");
 
-XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage",
-                                  "resource:///modules/loop/LoopStorage.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler",
                                   "resource:///modules/loop/MozLoopPushHandler.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
@@ -365,17 +362,16 @@ let MozLoopServiceInternal = {
    */
   set doNotDisturb(aFlag) {
     Services.prefs.setBoolPref("loop.do_not_disturb", Boolean(aFlag));
     this.notifyStatusChanged();
   },
 
   notifyStatusChanged: function(aReason = null) {
     log.debug("notifyStatusChanged with reason:", aReason);
-    LoopStorage.switchDatabase(gFxAOAuthProfile ? gFxAOAuthProfile.uid : null);
     Services.obs.notifyObservers(null, "loop-status-changed", aReason);
   },
 
   /**
    * Record an error and notify interested UI with the relevant user-facing strings attached.
    *
    * @param {String} errorType a key to identify the type of error. Only one
    *                           error of a type will be saved at a time. This value may be used to
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -148,24 +148,24 @@ loop.contacts = (function(_, mozL10n) {
         this.setState({showMenu: false});
       }
     },
 
     componentWillUnmount: function() {
       document.body.removeEventListener("click", this._onBodyClick);
     },
 
-    shouldComponentUpdate: function(nextProps, nextState) {
+    componentShouldUpdate: function(nextProps, nextState) {
       let currContact = this.props.contact;
       let nextContact = nextProps.contact;
       return (
         currContact.name[0] !== nextContact.name[0] ||
         currContact.blocked !== nextContact.blocked ||
-        getPreferredEmail(currContact).value !== getPreferredEmail(nextContact).value ||
-        nextState.showMenu !== this.state.showMenu
+        getPreferredEmail(currContact).value !==
+          getPreferredEmail(nextContact).value
       );
     },
 
     handleAction: function(actionName) {
       if (this.props.handleContactAction) {
         this.props.handleContactAction(this.props.contact, actionName);
       }
     },
@@ -210,78 +210,45 @@ loop.contacts = (function(_, mozL10n) {
         )
       );
     }
   });
 
   const ContactsList = React.createClass({displayName: 'ContactsList',
     mixins: [React.addons.LinkedStateMixin],
 
-    /**
-     * Contacts collection object
-     */
-    contacts: null,
-
-    /**
-     * User profile
-     */
-    _userProfile: null,
-
     getInitialState: function() {
       return {
+        contacts: {},
         importBusy: false,
         filter: "",
       };
     },
 
-    refresh: function(callback = function() {}) {
+    componentDidMount: function() {
       let contactsAPI = navigator.mozLoop.contacts;
 
-      this.handleContactRemoveAll();
-
       contactsAPI.getAll((err, contacts) => {
         if (err) {
-          callback(err);
-          return;
+          throw err;
         }
 
         // Add contacts already present in the DB. We do this in timed chunks to
         // circumvent blocking the main event loop.
         let addContactsInChunks = () => {
           contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => {
             this.handleContactAddOrUpdate(contact, false);
           });
           if (contacts.length) {
             setTimeout(addContactsInChunks, 0);
-          } else {
-            callback();
           }
           this.forceUpdate();
         };
 
         addContactsInChunks(contacts);
-      });
-    },
-
-    componentWillMount: function() {
-      // Take the time to initialize class variables that are used outside
-      // `this.state`.
-      this.contacts = {};
-      this._userProfile = navigator.mozLoop.userProfile;
-    },
-
-    componentDidMount: function() {
-      window.addEventListener("LoopStatusChanged", this._onStatusChanged);
-
-      this.refresh(err => {
-        if (err) {
-          throw err;
-        }
-
-        let contactsAPI = navigator.mozLoop.contacts;
 
         // Listen for contact changes/ updates.
         contactsAPI.on("add", (eventName, contact) => {
           this.handleContactAddOrUpdate(contact);
         });
         contactsAPI.on("remove", (eventName, contact) => {
           this.handleContactRemove(contact);
         });
@@ -289,55 +256,37 @@ loop.contacts = (function(_, mozL10n) {
           this.handleContactRemoveAll();
         });
         contactsAPI.on("update", (eventName, contact) => {
           this.handleContactAddOrUpdate(contact);
         });
       });
     },
 
-    componentWillUnmount: function() {
-      window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
-    },
-
-    _onStatusChanged: function() {
-      let profile = navigator.mozLoop.userProfile;
-      let currUid = this._userProfile ? this._userProfile.uid : null;
-      let newUid = profile ? profile.uid : null;
-      if (currUid != newUid) {
-        // On profile change (login, logout), reload all contacts.
-        this._userProfile = profile;
-        // The following will do a forceUpdate() for us.
-        this.refresh();
-      }
-    },
-
     handleContactAddOrUpdate: function(contact, render = true) {
-      let contacts = this.contacts;
+      let contacts = this.state.contacts;
       let guid = String(contact._guid);
       contacts[guid] = contact;
       if (render) {
         this.forceUpdate();
       }
     },
 
     handleContactRemove: function(contact) {
-      let contacts = this.contacts;
+      let contacts = this.state.contacts;
       let guid = String(contact._guid);
       if (!contacts[guid]) {
         return;
       }
       delete contacts[guid];
       this.forceUpdate();
     },
 
     handleContactRemoveAll: function() {
-      // Do not allow any race conditions when removing all contacts.
-      this.contacts = {};
-      this.forceUpdate();
+      this.setState({contacts: {}});
     },
 
     handleImportButtonClick: function() {
       this.setState({ importBusy: true });
       navigator.mozLoop.startImport({
         service: "google"
       }, (err, stats) => {
         this.setState({ importBusy: false });
@@ -410,21 +359,21 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     render: function() {
       let viewForItem = item => {
         return ContactDetail({key: item._guid, contact: item, 
                               handleContactAction: this.handleContactAction})
       };
 
-      let shownContacts = _.groupBy(this.contacts, function(contact) {
+      let shownContacts = _.groupBy(this.state.contacts, function(contact) {
         return contact.blocked ? "blocked" : "available";
       });
 
-      let showFilter = Object.getOwnPropertyNames(this.contacts).length >=
+      let showFilter = Object.getOwnPropertyNames(this.state.contacts).length >=
                        MIN_CONTACTS_FOR_FILTERING;
       if (showFilter) {
         let filter = this.state.filter.trim().toLocaleLowerCase();
         if (filter) {
           let filterFn = contact => {
             return contact.name[0].toLocaleLowerCase().contains(filter) ||
                    getPreferredEmail(contact).value.toLocaleLowerCase().contains(filter);
           };
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -148,24 +148,24 @@ loop.contacts = (function(_, mozL10n) {
         this.setState({showMenu: false});
       }
     },
 
     componentWillUnmount: function() {
       document.body.removeEventListener("click", this._onBodyClick);
     },
 
-    shouldComponentUpdate: function(nextProps, nextState) {
+    componentShouldUpdate: function(nextProps, nextState) {
       let currContact = this.props.contact;
       let nextContact = nextProps.contact;
       return (
         currContact.name[0] !== nextContact.name[0] ||
         currContact.blocked !== nextContact.blocked ||
-        getPreferredEmail(currContact).value !== getPreferredEmail(nextContact).value ||
-        nextState.showMenu !== this.state.showMenu
+        getPreferredEmail(currContact).value !==
+          getPreferredEmail(nextContact).value
       );
     },
 
     handleAction: function(actionName) {
       if (this.props.handleContactAction) {
         this.props.handleContactAction(this.props.contact, actionName);
       }
     },
@@ -210,78 +210,45 @@ loop.contacts = (function(_, mozL10n) {
         </li>
       );
     }
   });
 
   const ContactsList = React.createClass({
     mixins: [React.addons.LinkedStateMixin],
 
-    /**
-     * Contacts collection object
-     */
-    contacts: null,
-
-    /**
-     * User profile
-     */
-    _userProfile: null,
-
     getInitialState: function() {
       return {
+        contacts: {},
         importBusy: false,
         filter: "",
       };
     },
 
-    refresh: function(callback = function() {}) {
+    componentDidMount: function() {
       let contactsAPI = navigator.mozLoop.contacts;
 
-      this.handleContactRemoveAll();
-
       contactsAPI.getAll((err, contacts) => {
         if (err) {
-          callback(err);
-          return;
+          throw err;
         }
 
         // Add contacts already present in the DB. We do this in timed chunks to
         // circumvent blocking the main event loop.
         let addContactsInChunks = () => {
           contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => {
             this.handleContactAddOrUpdate(contact, false);
           });
           if (contacts.length) {
             setTimeout(addContactsInChunks, 0);
-          } else {
-            callback();
           }
           this.forceUpdate();
         };
 
         addContactsInChunks(contacts);
-      });
-    },
-
-    componentWillMount: function() {
-      // Take the time to initialize class variables that are used outside
-      // `this.state`.
-      this.contacts = {};
-      this._userProfile = navigator.mozLoop.userProfile;
-    },
-
-    componentDidMount: function() {
-      window.addEventListener("LoopStatusChanged", this._onStatusChanged);
-
-      this.refresh(err => {
-        if (err) {
-          throw err;
-        }
-
-        let contactsAPI = navigator.mozLoop.contacts;
 
         // Listen for contact changes/ updates.
         contactsAPI.on("add", (eventName, contact) => {
           this.handleContactAddOrUpdate(contact);
         });
         contactsAPI.on("remove", (eventName, contact) => {
           this.handleContactRemove(contact);
         });
@@ -289,55 +256,37 @@ loop.contacts = (function(_, mozL10n) {
           this.handleContactRemoveAll();
         });
         contactsAPI.on("update", (eventName, contact) => {
           this.handleContactAddOrUpdate(contact);
         });
       });
     },
 
-    componentWillUnmount: function() {
-      window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
-    },
-
-    _onStatusChanged: function() {
-      let profile = navigator.mozLoop.userProfile;
-      let currUid = this._userProfile ? this._userProfile.uid : null;
-      let newUid = profile ? profile.uid : null;
-      if (currUid != newUid) {
-        // On profile change (login, logout), reload all contacts.
-        this._userProfile = profile;
-        // The following will do a forceUpdate() for us.
-        this.refresh();
-      }
-    },
-
     handleContactAddOrUpdate: function(contact, render = true) {
-      let contacts = this.contacts;
+      let contacts = this.state.contacts;
       let guid = String(contact._guid);
       contacts[guid] = contact;
       if (render) {
         this.forceUpdate();
       }
     },
 
     handleContactRemove: function(contact) {
-      let contacts = this.contacts;
+      let contacts = this.state.contacts;
       let guid = String(contact._guid);
       if (!contacts[guid]) {
         return;
       }
       delete contacts[guid];
       this.forceUpdate();
     },
 
     handleContactRemoveAll: function() {
-      // Do not allow any race conditions when removing all contacts.
-      this.contacts = {};
-      this.forceUpdate();
+      this.setState({contacts: {}});
     },
 
     handleImportButtonClick: function() {
       this.setState({ importBusy: true });
       navigator.mozLoop.startImport({
         service: "google"
       }, (err, stats) => {
         this.setState({ importBusy: false });
@@ -410,21 +359,21 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     render: function() {
       let viewForItem = item => {
         return <ContactDetail key={item._guid} contact={item}
                               handleContactAction={this.handleContactAction} />
       };
 
-      let shownContacts = _.groupBy(this.contacts, function(contact) {
+      let shownContacts = _.groupBy(this.state.contacts, function(contact) {
         return contact.blocked ? "blocked" : "available";
       });
 
-      let showFilter = Object.getOwnPropertyNames(this.contacts).length >=
+      let showFilter = Object.getOwnPropertyNames(this.state.contacts).length >=
                        MIN_CONTACTS_FOR_FILTERING;
       if (showFilter) {
         let filter = this.state.filter.trim().toLocaleLowerCase();
         if (filter) {
           let filterFn = contact => {
             return contact.name[0].toLocaleLowerCase().contains(filter) ||
                    getPreferredEmail(contact).value.toLocaleLowerCase().contains(filter);
           };
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -485,19 +485,17 @@ loop.panel = (function(_, mozL10n) {
         });
       } else {
         this.props.notifications.remove(this.props.notifications.get("service-error"));
       }
     },
 
     _onStatusChanged: function() {
       var profile = navigator.mozLoop.userProfile;
-      var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
-      var newUid = profile ? profile.uid : null;
-      if (currUid != newUid) {
+      if (profile != this.state.userProfile) {
         // On profile change (login, logout), switch back to the default tab.
         this.selectTab("call");
       }
       this.setState({userProfile: profile});
       this.updateServiceErrors();
     },
 
     startForm: function(name, contact) {
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -485,19 +485,17 @@ loop.panel = (function(_, mozL10n) {
         });
       } else {
         this.props.notifications.remove(this.props.notifications.get("service-error"));
       }
     },
 
     _onStatusChanged: function() {
       var profile = navigator.mozLoop.userProfile;
-      var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
-      var newUid = profile ? profile.uid : null;
-      if (currUid != newUid) {
+      if (profile != this.state.userProfile) {
         // On profile change (login, logout), switch back to the default tab.
         this.selectTab("call");
       }
       this.setState({userProfile: profile});
       this.updateServiceErrors();
     },
 
     startForm: function(name, contact) {
--- a/browser/components/loop/test/mochitest/browser_LoopContacts.js
+++ b/browser/components/loop/test/mochitest/browser_LoopContacts.js
@@ -1,17 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const {LoopContacts} = Cu.import("resource:///modules/loop/LoopContacts.jsm", {});
-const {LoopStorage} = Cu.import("resource:///modules/loop/LoopStorage.jsm", {});
-
-XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
-                                   "@mozilla.org/uuid-generator;1",
-                                   "nsIUUIDGenerator");
 
 const kContacts = [{
   id: 1,
   name: ["Ally Avocado"],
   email: [{
     "pref": true,
     "type": ["work"],
     "value": "ally@mail.com"
@@ -400,36 +395,8 @@ add_task(function* () {
 // Test if the event emitter implementation doesn't leak and is working as expected.
 add_task(function* () {
   yield promiseLoadContacts();
 
   Assert.strictEqual(gExpectedAdds.length, 0, "No contact additions should be expected anymore");
   Assert.strictEqual(gExpectedRemovals.length, 0, "No contact removals should be expected anymore");
   Assert.strictEqual(gExpectedUpdates.length, 0, "No contact updates should be expected anymore");
 });
-
-// Test switching between different databases.
-add_task(function* () {
-  Assert.equal(LoopStorage.databaseName, "loop-default", "First active partition should be the default");
-  yield promiseLoadContacts();
-
-  let uuid = uuidgen.generateUUID().toString().replace(/[{}]+/g, "");
-  LoopStorage.switchDatabase(uuid);
-  Assert.equal(LoopStorage.databaseName, uuid, "The active partition should have changed");
-
-  yield promiseLoadContacts();
-
-  let contacts = yield promiseLoadContacts();
-  for (let i = 0, l = contacts.length; i < l; ++i) {
-    compareContacts(contacts[i], kContacts[i]);
-  }
-
-  LoopStorage.switchDatabase();
-  Assert.equal(LoopStorage.databaseName, "loop-default", "The active partition should have changed");
-
-  LoopContacts.getAll(function(err, contacts) {
-    Assert.equal(err, null, "There shouldn't be an error");
-
-    for (let i = 0, l = contacts.length; i < l; ++i) {
-      compareContacts(contacts[i], kContacts[i]);
-    }
-  });
-});