From 96c377da98cc1b6f4d78147ed530a527cac22023 Mon Sep 17 00:00:00 2001 From: Gregor Wagner Date: Tue, 3 Jul 2012 11:00:53 -0700 Subject: [PATCH] Bug 769245 - Contacts API: Add ContactEmail Type. r=sicking --- dom/contacts/ContactManager.js | 52 +++++++++++-- dom/contacts/ContactManager.manifest | 3 + dom/contacts/fallback/ContactDB.jsm | 41 +++++++++- dom/contacts/fallback/ContactService.jsm | 2 +- dom/contacts/tests/test_contacts_basics.html | 74 ++++++++++++++++--- .../contacts/nsIDOMContactProperties.idl | 9 ++- .../mochitest/general/test_interfaces.html | 1 + 7 files changed, 161 insertions(+), 21 deletions(-) diff --git a/dom/contacts/ContactManager.js b/dom/contacts/ContactManager.js index fbf60a524186..867c77792d49 100644 --- a/dom/contacts/ContactManager.js +++ b/dom/contacts/ContactManager.js @@ -2,7 +2,7 @@ * 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/. */ -"use strict" +"use strict"; /* static functions */ let DEBUG = 0; @@ -78,6 +78,29 @@ ContactAddress.prototype = { QueryInterface : XPCOMUtils.generateQI([nsIDOMContactAddress]) } +//ContactEmail + +const CONTACTEMAIL_CONTRACTID = "@mozilla.org/contactEmail;1"; +const CONTACTEMAIL_CID = Components.ID("{94811520-c11f-11e1-afa7-0800200c9a66}"); +const nsIDOMContactEmail = Components.interfaces.nsIDOMContactEmail; + +function ContactEmail(aType, aAddress) { + this.type = aType || null; + this.address = aAddress || null; +}; + +ContactEmail.prototype = { + + classID : CONTACTEMAIL_CID, + classInfo : XPCOMUtils.generateCI({classID: CONTACTEMAIL_CID, + contractID: CONTACTEMAIL_CONTRACTID, + classDescription: "ContactEmail", + interfaces: [nsIDOMContactEmail], + flags: nsIClassInfo.DOM_OBJECT}), + + QueryInterface : XPCOMUtils.generateQI([nsIDOMContactEmail]) +} + //ContactTelephone const CONTACTTELEPHONE_CONTRACTID = "@mozilla.org/contactTelephone;1"; @@ -133,10 +156,16 @@ Contact.prototype = { init: function init(aProp) { // Accept non-array strings for DOMString[] properties and convert them. - function _create(aField) { - if (typeof aField == "string") - return new Array(aField); - return aField; + function _create(aField) { + if (Array.isArray(aField)) { + for (let i = 0; i < aField.length; i++) { + if (typeof aField[i] !== "string") + aField[i] = String(aField[i]); + } + return aField; + } else if (aField != null) { + return [String(aField)]; + } }; this.name = _create(aProp.name) || null; @@ -146,7 +175,16 @@ Contact.prototype = { this.familyName = _create(aProp.familyName) || null; this.honorificSuffix = _create(aProp.honorificSuffix) || null; this.nickname = _create(aProp.nickname) || null; - this.email = _create(aProp.email) || null; + + if (aProp.email) { + aProp.email = Array.isArray(aProp.email) ? aProp.email : [aProp.email]; + this.email = new Array(); + for (let i = 0; i < aProp.email.length; i++) + this.email.push(new ContactEmail(aProp.email[i].type, aProp.email[i].address)); + } else { + this.email = null; + } + this.photo = _create(aProp.photo) || null; this.url = _create(aProp.url) || null; this.category = _create(aProp.category) || null; @@ -445,4 +483,4 @@ ContactManager.prototype = { } const NSGetFactory = XPCOMUtils.generateNSGetFactory( - [Contact, ContactManager, ContactProperties, ContactAddress, ContactTelephone, ContactFindOptions]) + [Contact, ContactManager, ContactProperties, ContactAddress, ContactTelephone, ContactFindOptions, ContactEmail]) diff --git a/dom/contacts/ContactManager.manifest b/dom/contacts/ContactManager.manifest index cb1864489913..7ee2428ee4f1 100644 --- a/dom/contacts/ContactManager.manifest +++ b/dom/contacts/ContactManager.manifest @@ -7,6 +7,9 @@ contract @mozilla.org/contactAddress;1 {eba48030-89e8-11e1-b0c4-0800200c9a66} component {82601b20-89e8-11e1-b0c4-0800200c9a66} ContactManager.js contract @mozilla.org/contactTelephone;1 {82601b20-89e8-11e1-b0c4-0800200c9a66} +component {94811520-c11f-11e1-afa7-0800200c9a66} ContactManager.js +contract @mozilla.org/contactEmail;1 {94811520-c11f-11e1-afa7-0800200c9a66} + component {e31daea0-0cb6-11e1-be50-0800200c9a66} ContactManager.js contract @mozilla.org/contactFindOptions;1 {e31daea0-0cb6-11e1-be50-0800200c9a66} diff --git a/dom/contacts/fallback/ContactDB.jsm b/dom/contacts/fallback/ContactDB.jsm index 8690dc5063ca..d4b4d5ad02a7 100644 --- a/dom/contacts/fallback/ContactDB.jsm +++ b/dom/contacts/fallback/ContactDB.jsm @@ -22,7 +22,7 @@ Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/IndexedDBHelper.jsm"); const DB_NAME = "contacts"; -const DB_VERSION = 2; +const DB_VERSION = 3; const STORE_NAME = "contacts"; function ContactDB(aGlobal) { @@ -101,6 +101,31 @@ ContactDB.prototype = { // Create new searchable indexes. objectStore.createIndex("tel", "search.tel", { unique: false, multiEntry: true }); objectStore.createIndex("category", "properties.category", { unique: false, multiEntry: true }); + } else if (currVersion == 2) { + debug("upgrade 2"); + // Create a new scheme for the email field. We move from an array of emailaddresses to an array of + // ContactEmail. + if (!objectStore) { + objectStore = aTransaction.objectStore(STORE_NAME); + } + // Delete old email index. + objectStore.deleteIndex("email"); + + // Upgrade existing email field in the DB. + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + debug("upgrade email1: " + JSON.stringify(cursor.value)); + cursor.value.properties.email = + cursor.value.properties.email.map(function(address) { return { address: address }; }); + cursor.update(cursor.value); + debug("upgrade email2: " + JSON.stringify(cursor.value)); + cursor.continue(); + } + }; + + // Create new searchable indexes. + objectStore.createIndex("email", "search.email", { unique: false, multiEntry: true }); } } }, @@ -172,8 +197,16 @@ ContactDB.prototype = { } } debug("lookup: " + JSON.stringify(contact.search[field])); + } else if (field == "email") { + let address = aContact.properties[field][i].address; + if (address && typeof address == "string") { + contact.search[field].push(address.toLowerCase()); + } } else { - contact.search[field].push(aContact.properties[field][i].toLowerCase()); + let val = aContact.properties[field][i]; + if (typeof val == "string") { + contact.search[field].push(val.toLowerCase()); + } } } } @@ -319,7 +352,9 @@ ContactDB.prototype = { request = index.mozGetAll(options.filterValue, limit); } else { // not case sensitive - let tmp = options.filterValue.toLowerCase(); + let tmp = typeof options.filterValue == "string" + ? options.filterValue.toLowerCase() + : options.filterValue.toString().toLowerCase(); let range = this._global.IDBKeyRange.bound(tmp, tmp + "\uFFFF"); let index = store.index(key + "LowerCase"); request = index.mozGetAll(range, limit); diff --git a/dom/contacts/fallback/ContactService.jsm b/dom/contacts/fallback/ContactService.jsm index 6a06d10aca56..c49516c2b7ed 100644 --- a/dom/contacts/fallback/ContactService.jsm +++ b/dom/contacts/fallback/ContactService.jsm @@ -2,7 +2,7 @@ * 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/. */ -"use strict" +"use strict"; let DEBUG = 0; if (DEBUG) diff --git a/dom/contacts/tests/test_contacts_basics.html b/dom/contacts/tests/test_contacts_basics.html index a028c624f741..5cf99cbce7aa 100644 --- a/dom/contacts/tests/test_contacts_basics.html +++ b/dom/contacts/tests/test_contacts_basics.html @@ -76,7 +76,8 @@ var properties1 = { givenName: ["Test1","Test2"], nickname: "nicktest", tel: [{type: "work", number: "123456"} , {type: "home", number: "+9-876-5432"}], - adr: adr1 + adr: adr1, + email: [{type: "work", address: "x@y.com"}] }; var properties2 = { @@ -88,7 +89,7 @@ var properties2 = { additionalName: "dummyadditionalName", nickname: "dummyNickname", tel: [{type: "test", number: "123456789"},{type: "home", number: "234567890"}], - email: ["a@b.c", "b@c.d"], + email: [{type: "test", address: "a@b.c"}, {address: "b@c.d"}], adr: [adr1, adr2], impp: ["im1", "im2"], org: ["org1", "org2"], @@ -148,6 +149,11 @@ function checkTel(tel1, tel2) { checkStr(tel1.number, tel2.number, "Same number"); } +function checkEmail(email1, email2) { + checkStr(email1.type, email2.type, "Same type"); + checkStr(email1.address, email2.address, "Same address"); +} + function checkContacts(contact1, contact2) { checkStr(contact1.name, contact2.name, "Same name"); checkStr(contact1.honorificPrefix, contact2.honorificPrefix, "Same honorificPrefix"); @@ -156,7 +162,6 @@ function checkContacts(contact1, contact2) { checkStr(contact1.familyName, contact2.familyName, "Same familyName"); checkStr(contact1.honorificSuffix, contact2.honorificSuffix, "Same honorificSuffix"); checkStr(contact1.nickname, contact2.nickname, "Same nickname"); - checkStr(contact1.email, contact2.email, "Same email"); checkStr(contact1.photo, contact2.photo, "Same photo"); checkStr(contact1.url, contact2.url, "Same url"); checkStr(contact1.category, contact2.category, "Same category"); @@ -169,6 +174,8 @@ function checkContacts(contact1, contact2) { is(contact1.sex, contact2.sex, "Same sex"); is(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity"); + for (var i in contact1.email) + checkEmail(contact1.email[i], contact2.email[i]); for (var i in contact1.adr) checkAddress(contact1.adr[i], contact2.adr[i]); for (var i in contact1.tel) @@ -269,6 +276,21 @@ var steps = [ }; req.onerror = onFailure; }, + function () { + ok(true, "Searching for exact email"); + var options = {filterBy: ["email"], + filterOp: "equals", + filterValue: properties1.email[0].address}; + 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(findResult1, createResult1); + next(); + }; + req.onerror = onFailure; + }, function () { ok(true, "Retrieving by substring and update"); mozContacts.oncontactchange = function(event) { @@ -616,14 +638,14 @@ var steps = [ }, function () { ok(true, "Modifying contact3"); - findResult1.email = (properties1.nickname); + findResult1.email = [{address: properties1.nickname}]; findResult1.nickname = "TEST"; var newContact = new mozContact(); newContact.init(findResult1); req = mozContacts.save(newContact); req.onsuccess = function () { var options = {filterBy: ["nickname", "email", "name"], - filterOp: "equals", + filterOp: "contains", filterValue: properties1.nickname}; // One contact has it in nickname and the other in email var req2 = mozContacts.find(options); @@ -741,7 +763,7 @@ var steps = [ ok(true, "Searching contacts by email"); var options = {filterBy: ["email"], filterOp: "contains", - filterValue: properties2.email[0].substring(0, 4)}; + filterValue: properties2.email[0].address.substring(0, 4)}; req = mozContacts.find(options); req.onsuccess = function () { ok(req.result.length == 1, "Found exactly 1 contact."); @@ -865,12 +887,12 @@ var steps = [ ok(true, "Testing clone contact2"); var cloned = new mozContact(createResult1); ok(cloned.id != createResult1.id, "Cloned contact has new ID"); - cloned.email = "new email!"; + cloned.email = {address: "new email!"}; cloned.givenName = "Tom"; req = mozContacts.save(cloned); req.onsuccess = function () { ok(cloned.id, "The contact now has an ID."); - ok(cloned.email == "new email!", "Same Email"); + ok(cloned.email.address == "new email!", "Same Email"); ok(createResult1.email != cloned.email, "Clone has different email"); ok(cloned.givenName == "Tom", "New Name"); next(); @@ -892,7 +914,7 @@ var steps = [ function () { ok(true, "Search with redundant fields should only return 1 contact"); createResult1 = new mozContact(); - createResult1.init({name: "XXX", nickname: "XXX", email: "XXX", tel: {number: "XXX"}}); + createResult1.init({name: "XXX", nickname: "XXX", email: [{address: "XXX"}], tel: {number: "XXX"}}); req = mozContacts.save(createResult1); req.onsuccess = function() { var options = {filterBy: [], @@ -1098,6 +1120,40 @@ var steps = [ } req.onerror = onFailure; }, + function () { + ok(true, "Adding empty contact"); + createResult1 = new mozContact(); + createResult1.init({givenName: 5}); + req = navigator.mozContacts.save(createResult1); + req.onsuccess = function () { + ok(createResult1.id, "The contact now has an ID."); + sample_id1 = createResult1.id; + next(); + }; + req.onerror = onFailure; + }, + function () { + ok(true, "Test category search with equals"); + var options = {filterBy: ["givenName"], + filterOp: "contains", + filterValue: 5}; + req = mozContacts.find(options); + req.onsuccess = function () { + ok(req.result.length == 1, "1 Entry."); + checkContacts(req.result[0], createResult1); + 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(); diff --git a/dom/interfaces/contacts/nsIDOMContactProperties.idl b/dom/interfaces/contacts/nsIDOMContactProperties.idl index 240ec273837f..082bad9575f1 100644 --- a/dom/interfaces/contacts/nsIDOMContactProperties.idl +++ b/dom/interfaces/contacts/nsIDOMContactProperties.idl @@ -25,6 +25,13 @@ interface nsIDOMContactTelephone : nsISupports attribute DOMString number; }; +[scriptable, uuid(94811520-c11f-11e1-afa7-0800200c9a66)] +interface nsIDOMContactEmail : nsISupports +{ + attribute DOMString type; + attribute DOMString address; +}; + [scriptable, uuid(e31daea0-0cb6-11e1-be50-0800200c9a66)] interface nsIDOMContactFindOptions : nsISupports { @@ -46,7 +53,7 @@ interface nsIDOMContactProperties : nsISupports attribute jsval familyName; // DOMString[] attribute jsval honorificSuffix; // DOMString[] attribute jsval nickname; // DOMString[] - attribute jsval email; // DOMString[] + attribute jsval email; // ContactEmail[] attribute jsval photo; // DOMString[] attribute jsval url; // DOMString[] attribute jsval category; // DOMString[] diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 2ccb91aae021..c7560a9e6f07 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -519,6 +519,7 @@ var interfaceNamesInGlobalScope = "WebGLActiveInfo", "SVGGradientElement", "ContactTelephone", + "ContactEmail", "SVGFitToViewBox", "SVGAElement" ]