Bug 734198 - Contacts API: Add Sorting. r=bent
authorGregor Wagner <anygregor@gmail.com>
Mon, 02 Apr 2012 16:39:57 -0700
changeset 90877 3ad6cc527d5c7f293d1a9978998d46ff751eeb5a
parent 90876 9cc500d2e6c8196edddcee609a7de4fc519ccac4
child 90878 b1a9e8a536bfdd7857d6e3648efa8768ca7fb7dc
push id22394
push userMs2ger@gmail.com
push dateTue, 03 Apr 2012 07:22:53 +0000
treeherdermozilla-central@9894cd999781 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs734198
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 734198 - Contacts API: Add Sorting. r=bent
dom/contacts/ContactManager.js
dom/contacts/fallback/ContactDB.jsm
dom/contacts/fallback/ContactService.jsm
dom/contacts/tests/test_contacts_basics.html
dom/interfaces/contacts/nsIDOMContactProperties.idl
--- a/dom/contacts/ContactManager.js
+++ b/dom/contacts/ContactManager.js
@@ -14,17 +14,17 @@ else
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 
-XPCOMUtils.defineLazyGetter(Services, "rs", function() {
+XPCOMUtils.defineLazyGetter(Services, "DOMRequest", function() {
   return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService);
 });
 
 XPCOMUtils.defineLazyGetter(this, "cpmm", function() {
   return Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
 });
 
 const nsIClassInfo            = Ci.nsIClassInfo;
@@ -74,26 +74,17 @@ ContactAddress.prototype = {
 }
 
 //ContactFindOptions
 
 const CONTACTFINDOPTIONS_CONTRACTID = "@mozilla.org/contactFindOptions;1";
 const CONTACTFINDOPTIONS_CID        = Components.ID("{e31daea0-0cb6-11e1-be50-0800200c9a66}");
 const nsIDOMContactFindOptions      = Components.interfaces.nsIDOMContactFindOptions;
 
-function ContactFindOptions(aFilterValue, aFilterBy, aFilterOp, aFilterLimit) {
-  this.filterValue = aFilterValue || '';
-
-  this.filterBy = new Array();
-  for (let field in aFilterBy)
-    this.filterBy.push(field);
-
-  this.filterOp = aFilterOp || '';
-  this.filterLimit = aFilterLimit || 0;
-};
+function ContactFindOptions() { };
 
 ContactFindOptions.prototype = {
 
   classID : CONTACTFINDOPTIONS_CID,
   classInfo : XPCOMUtils.generateCI({classID: CONTACTFINDOPTIONS_CID,
                                      contractID: CONTACTFINDOPTIONS_CONTRACTID,
                                      classDescription: "ContactFindOptions",
                                      interfaces: [nsIDOMContactFindOptions],
@@ -277,35 +268,35 @@ ContactManager.prototype = {
     let contacts = msg.contacts;
 
     switch (aMessage.name) {
       case "Contacts:Find:Return:OK":
         let req = this.getRequest(msg.requestID);
         if (req) {
           let result = this._convertContactsArray(contacts);
           debug("result: " + JSON.stringify(result));
-          Services.rs.fireSuccess(req, result);
+          Services.DOMRequest.fireSuccess(req, result);
         } else {
           debug("no request stored!" + msg.requestID);
         }
         break;
       case "Contact:Save:Return:OK":
       case "Contacts:Clear:Return:OK":
       case "Contact:Remove:Return:OK":
         req = this.getRequest(msg.requestID);
         if (req)
-          Services.rs.fireSuccess(req, null);
+          Services.DOMRequest.fireSuccess(req, null);
         break;
       case "Contacts:Find:Return:KO":
       case "Contact:Save:Return:KO":
       case "Contact:Remove:Return:KO":
       case "Contacts:Clear:Return:KO":
         req = this.getRequest(msg.requestID);
         if (req)
-          Services.rs.fireError(req, msg.errorMsg);
+          Services.DOMRequest.fireError(req, msg.errorMsg);
         break;
       default: 
         debug("Wrong message: " + aMessage.name);
     }
     this.removeRequest(msg.requestID);
   },
 
   find: function(aOptions) {
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -372,49 +372,53 @@ ContactDB.prototype = {
     // lookup for all keys
     if (options.filterBy.length == 0) {
       debug("search in all fields!" + JSON.stringify(store.indexNames));
       for(let myIndex = 0; myIndex < store.indexNames.length; myIndex++) {
         fields = Array.concat(fields, store.indexNames[myIndex])
       }
     }
 
+    // Sorting functions takes care of limit if set.
+    let limit = options.sortBy === 'undefined' ? options.filterLimit : null;
+
     let filter_keys = fields.slice();
     for (let key = filter_keys.shift(); key; key = filter_keys.shift()) {
       let request;
       if (key == "id") {
         // store.get would return an object and not an array
         request = store.getAll(options.filterValue);
       } else if (options.filterOp == "equals") {
         debug("Getting index: " + key);
         // case sensitive
         let index = store.index(key);
-        request = index.getAll(options.filterValue, options.filterLimit);
+        request = index.getAll(options.filterValue, limit);
       } else {
         // not case sensitive
         let tmp = options.filterValue.toLowerCase();
         let range = this._global.IDBKeyRange.bound(tmp, tmp + "\uFFFF");
         let index = store.index(key + "LowerCase");
-        request = index.getAll(range, options.filterLimit);
+        request = index.getAll(range, limit);
       }
       if (!txn.result)
         txn.result = {};
 
       request.onsuccess = function (event) {
         debug("Request successful. Record count:" + event.target.result.length);
         for (let i in event.target.result)
           txn.result[event.target.result[i].id] = this.makeExport(event.target.result[i]);
       }.bind(this);
     }
   },
 
   _findAll: function _findAll(txn, store, options) {
     debug("ContactDB:_findAll:  " + JSON.stringify(options));
     if (!txn.result)
       txn.result = {};
-
-    store.getAll(null, options.filterLimit).onsuccess = function (event) {
+    // Sorting functions takes care of limit if set.
+    let limit = options.sortBy === 'undefined' ? options.filterLimit : null;
+    store.getAll(null, limit).onsuccess = function (event) {
       debug("Request successful. Record count:", event.target.result.length);
       for (let i in event.target.result)
         txn.result[event.target.result[i].id] = this.makeExport(event.target.result[i]);
     }.bind(this);
   }
 };
--- a/dom/contacts/fallback/ContactService.jsm
+++ b/dom/contacts/fallback/ContactService.jsm
@@ -59,25 +59,42 @@ let DOMContactManager = {
     Services.obs.removeObserver(this, "profile-before-change");
     ppmm = null;
     this._messages = null;
     if (this._db)
       this._db.close();
   },
 
   receiveMessage: function(aMessage) {
+    function sortfunction(a, b){
+      let x, y;
+      if (a.properties[msg.findOptions.sortBy])
+        x = a.properties[msg.findOptions.sortBy][0].toLowerCase();
+      if (b.properties[msg.findOptions.sortBy])
+        y = b.properties[msg.findOptions.sortBy][0].toLowerCase();
+      if (msg.findOptions == 'ascending')
+        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+      return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+    }
     debug("Fallback DOMContactManager::receiveMessage " + aMessage.name);
     let msg = aMessage.json;
     switch (aMessage.name) {
       case "Contacts:Find":
         let result = new Array();
         this._db.find(
           function(contacts) {
             for (let i in contacts)
               result.push(contacts[i]);
+            if (msg.findOptions.sortOrder !== 'undefined' && msg.findOptions.sortBy !== 'undefined') {
+              debug('sortBy: ' + msg.findOptions.sortBy + ', sortOrder: ' + msg.findOptions.sortOrder );
+              result.sort(sortfunction);
+              if (msg.findOptions.filterLimit)
+                result = result.slice(0, msg.findOptions.filterLimit);
+            }
+
             debug("result:" + JSON.stringify(result));
             ppmm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result});
           }.bind(this),
           function(aErrorMsg) { ppmm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }) }.bind(this), 
           msg.findOptions);
         break;
       case "Contact:Save":
         this._db.saveContact(msg.contact, function() { ppmm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID }); }.bind(this), 
--- a/dom/contacts/tests/test_contacts_basics.html
+++ b/dom/contacts/tests/test_contacts_basics.html
@@ -23,16 +23,45 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 Components.classes["@mozilla.org/permissionmanager;1"]
           .getService(Components.interfaces.nsIPermissionManager)
           .add(SpecialPowers.getDocumentURIObject(window.document),
                "webcontacts-manage",
                Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
 
+// For Sorting
+var c1 = {
+  name: "a",
+  familyName: ["a"],
+  givenName: ["a"],
+};
+
+var c2 = {
+  name: "b",
+  familyName: ["b"],
+  givenName: ["b"],
+};
+
+var c3 = {
+  name: "c",
+  familyName: ["c","x"],
+  givenName: ["c","x"],
+};
+
+var c4 = {
+  name: "d",
+  familyName: ["d","e"],
+  givenName: ["d","e"],
+};
+
+var c5 = {
+  name: "empty"
+};
+
 var adr1 = {
   streetAddress: "street 1",
   locality: "locality 1",
   region: "region 1",
   postalCode: "postal code 1",
   countryName: "country 1"
 };
 
@@ -636,16 +665,28 @@ var steps = [
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 10, "10 Entries.");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
+    ok(true, "Retrieving all contacts with limit 10 and sorted");
+    var options = { filterLimit: 10,
+                    sortBy: 'FamilyName',
+                    sortOrder: 'descending' };
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 10, "10 Entries.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
     ok(true, "Retrieving all contacts2");
     var options = {filterBy: ["name"],
                    filterOp: "contains",
                    filterValue: properties1.name[0].substring(0, 4)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 100, "100 Entries.");
       checkContacts(createResult1, req.result[99]);
@@ -739,16 +780,131 @@ var steps = [
     req = mozContacts.clear()
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact();
+    createResult1.init(c3);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c3, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact();
+    createResult1.init(c2);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c2, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact();
+    createResult1.init(c4);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c4, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact();
+    createResult1.init(c1);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c1, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    var options = {sortBy: "familyName",
+                   sortOrder: "ascending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 4, "4 results");
+      checkContacts(req.result[0], c1);
+      checkContacts(req.result[1], c2);
+      checkContacts(req.result[2], c3);
+      checkContacts(req.result[3], c4);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    var options = {sortBy: "familyName",
+                   sortOrder: "descending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 4, "4 results");
+      checkContacts(req.result[0], c4);
+      checkContacts(req.result[1], c3);
+      checkContacts(req.result[2], c2);
+      checkContacts(req.result[3], c1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact();
+    createResult1.init(c5);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c5, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting with empty string");
+    var options = {sortBy: "familyName",
+                   sortOrder: "ascending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 5, "5 results");
+      checkContacts(req.result[0], c5);
+      checkContacts(req.result[1], c1);
+      checkContacts(req.result[2], c2);
+      checkContacts(req.result[3], c3);
+      checkContacts(req.result[4], c4);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
     ok(true, "all done!\n");
     clearTemps();
 
     SimpleTest.finish();
   }
 ];
 
 function next() {
--- a/dom/interfaces/contacts/nsIDOMContactProperties.idl
+++ b/dom/interfaces/contacts/nsIDOMContactProperties.idl
@@ -18,16 +18,18 @@ interface nsIDOMContactAddress : nsISupp
 };
 
 [scriptable, uuid(e31daea0-0cb6-11e1-be50-0800200c9a66)]
 interface nsIDOMContactFindOptions : nsISupports
 {
   attribute DOMString filterValue;  // e.g. "Tom"
   attribute DOMString filterOp;     // e.g. "contains"
   attribute jsval filterBy;         // DOMString[], e.g. ["givenName", "nickname"]
+  attribute DOMString sortBy;       // e.g. "givenName"
+  attribute DOMString sortOrder;    // e.g. "descending"
   attribute unsigned long filterLimit;
 };
 
 [scriptable, uuid(53ed7c20-ceda-11e0-9572-0800200c9a66)]
 interface nsIDOMContactProperties : nsISupports
 {
   attribute jsval         name;               // DOMString[]
   attribute jsval         honorificPrefix;    // DOMString[]