Bug 750768 - Contacts API: add DB modification event. r=fabrice
authorGregor Wagner <anygregor@gmail.com>
Tue, 08 May 2012 11:42:41 -0700
changeset 93518 2e7efc12fd0bc1455b6fd793b161d4714da23013
parent 93517 29d098de41ff65d7e89b859e2c7c49fd315f7f85
child 93519 81535cbb76bca9e61cfd102b0c66fb38771545fd
push id22639
push userryanvm@gmail.com
push dateWed, 09 May 2012 01:32:57 +0000
treeherdermozilla-central@bbaa66c0efec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs750768
milestone15.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 750768 - Contacts API: add DB modification event. r=fabrice
dom/contacts/ContactManager.js
dom/contacts/ContactManager.manifest
dom/contacts/fallback/ContactService.jsm
dom/contacts/tests/test_contacts_basics.html
dom/interfaces/contacts/nsIDOMContactManager.idl
dom/interfaces/contacts/nsIDOMContactProperties.idl
--- a/dom/contacts/ContactManager.js
+++ b/dom/contacts/ContactManager.js
@@ -170,26 +170,38 @@ Contact.prototype = {
                                      flags: nsIClassInfo.DOM_OBJECT}),
 
   QueryInterface : XPCOMUtils.generateQI([nsIDOMContact, nsIDOMContactProperties])
 }
 
 // ContactManager
 
 const CONTACTMANAGER_CONTRACTID = "@mozilla.org/contactManager;1";
-const CONTACTMANAGER_CID        = Components.ID("{50a820b0-ced0-11e0-9572-0800200c9a66}");
+const CONTACTMANAGER_CID        = Components.ID("{d9ca0950-93d1-11e1-b0c4-0800200c9a66}");
 const nsIDOMContactManager      = Components.interfaces.nsIDOMContactManager;
 
 function ContactManager()
 {
   debug("Constructor");
 }
 
 ContactManager.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
+  _oncontactchange: null,
+
+  set oncontactchange(aCallback) {
+    if (this.hasPrivileges)
+      this._oncontactchange = aCallback;
+    else
+      throw Components.results.NS_ERROR_FAILURE;
+  },
+
+  get oncontactchange() {
+    return this._oncontactchange;
+  },
 
   save: function save(aContact) {
     let request;
     if (this.hasPrivileges) {
       debug("save: " + JSON.stringify(aContact) + " :" + aContact.id);
       let newContact = {};
       newContact.properties = {
         name:            [],
@@ -211,39 +223,43 @@ ContactManager.prototype = {
         impp:            [],
         anniversary:     null,
         sex:             null,
         genderIdentity:  null
       };
       for (let field in newContact.properties)
         newContact.properties[field] = aContact[field];
 
+      let reason;
       if (aContact.id == "undefined") {
         // for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes
         // 25c00f0190e5c545b4d421E2ddbab9e0
-        aContact.id = this._getRandomId().replace('-', '').replace('{', '').replace('}', '');
+        aContact.id = this._getRandomId().replace('-', '', 'g').replace('{', '').replace('}', '');
+        reason = "create";
+      } else {
+        reason = "update";
       }
 
       this._setMetaData(newContact, aContact);
       debug("send: " + JSON.stringify(newContact));
       request = this.createRequest();
       cpmm.sendAsyncMessage("Contact:Save", {contact: newContact,
-                                             requestID: this.getRequestId(request)});
+                                             requestID: this.getRequestId({request: request, reason: reason })});
       return request;
     } else {
       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
     }
   },
 
   remove: function removeContact(aRecord) {
     let request;
     if (this.hasPrivileges) {
       request = this.createRequest();
       cpmm.sendAsyncMessage("Contact:Remove", {id: aRecord.id,
-                                               requestID: this.getRequestId(request)});
+                                               requestID: this.getRequestId({request: request, reason: "remove"})});
       return request;
     } else {
       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
     }
   },
 
   _setMetaData: function(aNewContact, aRecord) {
     aNewContact.id = aRecord.id;
@@ -268,60 +284,66 @@ 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.DOMRequest.fireSuccess(req, result);
+          Services.DOMRequest.fireSuccess(req.request, 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.DOMRequest.fireSuccess(req, null);
+          Services.DOMRequest.fireSuccess(req.request, null);
+
+        // Fire oncontactchange event
+        if (this._oncontactchange) {
+          let event = new MozContactEvent(msg.contactID, req.reason);
+          this._oncontactchange.handleEvent(event);
+        }
         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.DOMRequest.fireError(req, msg.errorMsg);
+          Services.DOMRequest.fireError(req.request, msg.errorMsg);
         break;
       default: 
         debug("Wrong message: " + aMessage.name);
     }
     this.removeRequest(msg.requestID);
   },
 
   find: function(aOptions) {
     let request;
     if (this.hasPrivileges) {
       request = this.createRequest();
       cpmm.sendAsyncMessage("Contacts:Find", {findOptions: aOptions, 
-                                              requestID: this.getRequestId(request)});
+                                              requestID: this.getRequestId({request: request, reason: "find"})});
       return request;
     } else {
       debug("find not allowed");
       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
     }
   },
 
   clear: function() {
     let request;
     if (this.hasPrivileges) {
       request = this.createRequest();
-      cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId(request)});
+      cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({request: request, reason: "remove"})});
       return request;
     } else {
       debug("clear not allowed");
       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
     }
   },
 
   init: function(aWindow) {
@@ -341,19 +363,53 @@ ContactManager.prototype = {
                  Ci.nsIPermissionManager.ALLOW_ACTION : 
                  Services.perms.testExactPermission(principal.URI, "webcontacts-manage");
  
     //only pages with perm set can use the contacts
     this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
     debug("has privileges :" + this.hasPrivileges);
   },
 
+  // Called from DOMRequestIpcHelper
+  uninit: function uninit() {
+    debug("uninit call");
+    if (this._oncontactchange)
+      this._oncontactchange = null;
+  },
+
   classID : CONTACTMANAGER_CID,
   QueryInterface : XPCOMUtils.generateQI([nsIDOMContactManager, Ci.nsIDOMGlobalPropertyInitializer]),
 
   classInfo : XPCOMUtils.generateCI({classID: CONTACTMANAGER_CID,
                                      contractID: CONTACTMANAGER_CONTRACTID,
                                      classDescription: "ContactManager",
                                      interfaces: [nsIDOMContactManager],
                                      flags: nsIClassInfo.DOM_OBJECT})
 }
 
+// MozContactEvent object
+function MozContactEvent(aContactID, aReason) {
+  debug("ContactEventConstr: " + aContactID + ", " + aReason);
+  this._contactID = aContactID;
+  this._reason = aReason;
+}
+
+MozContactEvent.prototype = {
+  get contactID() {
+    return this._contactID;
+  },
+
+  get reason() {
+    return this._reason;
+  },
+
+  classID: Components.ID("{a8cd4ba0-93d1-11e1-b0c4-0800200c9a66}"),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMContactEvent]),
+
+  classInfo: XPCOMUtils.generateCI({classID: Components.ID("{a8cd4ba0-93d1-11e1-b0c4-0800200c9a66}"),
+                                    contractID: "@mozilla.org/contact-event;1",
+                                    interfaces: [Ci.mozIDOMContactEvent],
+                                    flags: Ci.nsIClassInfo.DOM_OBJECT,
+                                    classDescription: "Contact Change Event"})
+}
+
 const NSGetFactory = XPCOMUtils.generateNSGetFactory([Contact, ContactManager, ContactProperties, ContactAddress, ContactFindOptions])
--- a/dom/contacts/ContactManager.manifest
+++ b/dom/contacts/ContactManager.manifest
@@ -6,11 +6,11 @@ contract @mozilla.org/contactAddress;1 {
 
 component {e31daea0-0cb6-11e1-be50-0800200c9a66} ContactManager.js
 contract @mozilla.org/contactFindOptions;1 {e31daea0-0cb6-11e1-be50-0800200c9a66}
 
 component {da0f7040-388b-11e1-b86c-0800200c9a66} ContactManager.js
 contract @mozilla.org/contact;1 {da0f7040-388b-11e1-b86c-0800200c9a66}
 category JavaScript-global-constructor mozContact @mozilla.org/contact;1
 
-component {50a820b0-ced0-11e0-9572-0800200c9a66} ContactManager.js
-contract @mozilla.org/contactManager;1 {50a820b0-ced0-11e0-9572-0800200c9a66}
+component {d9ca0950-93d1-11e1-b0c4-0800200c9a66} ContactManager.js
+contract @mozilla.org/contactManager;1 {d9ca0950-93d1-11e1-b0c4-0800200c9a66}
 category JavaScript-navigator-property mozContacts @mozilla.org/contactManager;1
--- a/dom/contacts/fallback/ContactService.jsm
+++ b/dom/contacts/fallback/ContactService.jsm
@@ -90,28 +90,35 @@ let DOMContactManager = {
               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), 
+          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), 
-                             function(aErrorMsg) { ppmm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
+        this._db.saveContact(
+          msg.contact, 
+          function() { ppmm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID, contactID: msg.contact.id }); }.bind(this),
+          function(aErrorMsg) { ppmm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
+        );
         break;
       case "Contact:Remove":
-        this._db.removeContact(msg.id, 
-                               function() { ppmm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID }); }.bind(this), 
-                               function(aErrorMsg) { ppmm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
+        this._db.removeContact(
+          msg.id, 
+          function() { ppmm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID, contactID: msg.id }); }.bind(this),
+          function(aErrorMsg) { ppmm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
+        );
         break;
       case "Contacts:Clear":
-        this._db.clear(function() { ppmm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this),
-                       function(aErrorMsg) { ppmm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
+        this._db.clear(
+          function() { ppmm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this),
+          function(aErrorMsg) { ppmm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
+        );
     }
   }
 }
 
 DOMContactManager.init();
--- a/dom/contacts/tests/test_contacts_basics.html
+++ b/dom/contacts/tests/test_contacts_basics.html
@@ -226,16 +226,22 @@ var steps = [
       req2.onerror = onFailure;
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact1");
     createResult1 = new mozContact();
     createResult1.init(properties1);
+
+    mozContacts.oncontactchange = function(event) {
+      is(event.contactID, createResult1.id, "Same contactID");
+      is(event.reason, "create", "Same reason");
+    }
+
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       checkContacts(properties1, createResult1);
       next();
     };
     req.onerror = onFailure;
@@ -251,20 +257,144 @@ var steps = [
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
+    ok(true, "Retrieving by substring and update");
+    mozContacts.oncontactchange = function(event) {
+       is(event.contactID, findResult1.id, "Same contactID");
+       is(event.reason, "update", "Same reason");
+     }
+    var options = {filterBy: ["name"],
+                   filterOp: "contains",
+                   filterValue: properties1.name.substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      findResult1.jobTitle = ["new Job"];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact");
+    mozContacts.oncontactchange = function(event) {
+       is(event.contactID, createResult2.id, "Same contactID");
+       is(event.reason, "create", "Same reason");
+     }
+    createResult2 = new mozContact();
+    createResult2.init({name: "newName"});
+    req = navigator.mozContacts.save(createResult2);
+    req.onsuccess = function () {
+      ok(createResult2.id, "The contact now has an ID.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring");
+    var options = {filterBy: ["name"],
+                   filterOp: "contains",
+                   filterValue: properties1.name.substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      checkContacts(createResult1, findResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Remove contact1");
+    mozContacts.oncontactchange = function(event) {
+      is(event.contactID, createResult1.id, "Same contactID");
+      is(event.reason, "remove", "Same reason");
+    }
+    req = navigator.mozContacts.remove(createResult1);
+    req.onsuccess = function () {
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring");
+    var options = {filterBy: ["name"],
+                   filterOp: "contains",
+                   filterValue: properties1.name.substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 0, "Found no contact.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Remove contact2");
+    mozContacts.oncontactchange = function(event) {
+      is(event.contactID, createResult2.id, "Same contactID");
+      is(event.reason, "remove", "Same reason");
+    }
+    req = navigator.mozContacts.remove(createResult2);
+    req.onsuccess = function () {
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring");
+    var options = {filterBy: ["name"],
+                   filterOp: "contains",
+                   filterValue: properties1.name.substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 0, "Found no contact.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    mozContacts.oncontactchange = function(event) {
+      is(event.contactID, "undefined", "Same contactID");
+      is(event.reason, "remove", "Same reason");
+    }
+    req = mozContacts.clear();
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact with properties1");
+    createResult1 = new mozContact();
+    createResult1.init(properties1);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      checkContacts(properties1, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
     ok(true, "Retrieving by substring tel1");
     var options = {filterBy: ["tel"],
                    filterOp: "contains",
                    filterValue: properties1.tel[1].substring(1,5)};
+    mozContacts.oncontactchange = null;
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
--- a/dom/interfaces/contacts/nsIDOMContactManager.idl
+++ b/dom/interfaces/contacts/nsIDOMContactManager.idl
@@ -1,33 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "domstubs.idl"
 #include "nsIDOMContactProperties.idl"
+#include "nsIDOMEvent.idl"
 
 interface nsIArray;
 interface nsIDOMContactFindOptions;
 interface nsIDOMContactProperties;
 interface nsIDOMDOMRequest;
 
 [scriptable, uuid(da0f7040-388b-11e1-b86c-0800200c9a66)]
 interface nsIDOMContact : nsIDOMContactProperties
 {
   attribute DOMString id;
   readonly attribute jsval     published;
   readonly attribute jsval     updated;
   
   void init(in nsIDOMContactProperties properties);  // Workaround BUG 723206
 };
 
-[scriptable, uuid(50a820b0-ced0-11e0-9572-0800200c9a66)]
+[scriptable, uuid(a8cd4ba0-93d1-11e1-b0c4-0800200c9a66)]
+interface mozIDOMContactEvent : nsIDOMEvent
+{
+  readonly attribute DOMString contactID;
+  readonly attribute DOMString reason;
+};
+
+[scriptable, uuid(d9ca0950-93d1-11e1-b0c4-0800200c9a66)]
 interface nsIDOMContactManager : nsISupports
 {
   nsIDOMDOMRequest find(in nsIDOMContactFindOptions options);
 
   nsIDOMDOMRequest clear();
 
   nsIDOMDOMRequest save(in nsIDOMContact contact);
   
   nsIDOMDOMRequest remove(in nsIDOMContact contact);
-};
\ No newline at end of file
+
+  attribute nsIDOMEventListener oncontactchange;
+};
--- a/dom/interfaces/contacts/nsIDOMContactProperties.idl
+++ b/dom/interfaces/contacts/nsIDOMContactProperties.idl
@@ -46,9 +46,9 @@ interface nsIDOMContactProperties : nsIS
   attribute jsval         tel;                // DOMString[]
   attribute jsval         org;                // DOMString[]
   attribute jsval         bday;               // Date
   attribute jsval         note;               // DOMString[]
   attribute jsval         impp;               // DOMString[]
   attribute jsval         anniversary;        // Date
   attribute jsval         sex;                // DOMString
   attribute jsval         genderIdentity;     // DOMString
-};
\ No newline at end of file
+};