Bug 750768 - Contacts API: add DB modification event. r=fabrice
authorGregor Wagner <anygregor@gmail.com>
Tue, 08 May 2012 11:42:41 -0700
changeset 95785 2e7efc12fd0bc1455b6fd793b161d4714da23013
parent 95784 29d098de41ff65d7e89b859e2c7c49fd315f7f85
child 95786 81535cbb76bca9e61cfd102b0c66fb38771545fd
push id1439
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 20:19:22 +0000
treeherdermozilla-aurora@ea74834dccd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs750768
milestone15.0a1
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
+};