diff --git a/CLOBBER b/CLOBBER index a797fec2019e..96904d121b53 100644 --- a/CLOBBER +++ b/CLOBBER @@ -17,4 +17,5 @@ # # Modifying this file will now automatically clobber the buildbot machines \o/ # -Bug 916267 needs a clobber on Windows. \ No newline at end of file + +Bug 901789 needs a clobber. diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 1aa96719928e..406392dfb62a 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -177,6 +177,7 @@ #endif @BINPATH@/components/dom_camera.xpt @BINPATH@/components/dom_canvas.xpt +@BINPATH@/components/dom_contacts.xpt @BINPATH@/components/dom_alarm.xpt @BINPATH@/components/dom_core.xpt @BINPATH@/components/dom_css.xpt diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 02b6967d10e9..2538fec5e6a0 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -187,6 +187,7 @@ #endif @BINPATH@/components/dom_camera.xpt @BINPATH@/components/dom_canvas.xpt +@BINPATH@/components/dom_contacts.xpt @BINPATH@/components/dom_alarm.xpt @BINPATH@/components/dom_core.xpt @BINPATH@/components/dom_css.xpt diff --git a/dom/contacts/ContactManager.js b/dom/contacts/ContactManager.js index 68d7de233ed9..64a6816ef29e 100644 --- a/dom/contacts/ContactManager.js +++ b/dom/contacts/ContactManager.js @@ -15,9 +15,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); -XPCOMUtils.defineLazyServiceGetter(Services, "DOMRequest", - "@mozilla.org/dom/dom-request-service;1", - "nsIDOMRequestService"); +XPCOMUtils.defineLazyGetter(Services, "DOMRequest", function() { + return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService); +}); XPCOMUtils.defineLazyServiceGetter(this, "pm", "@mozilla.org/permissionmanager;1", @@ -29,256 +29,454 @@ XPCOMUtils.defineLazyServiceGetter(this, "cpmm", const CONTACTS_SENDMORE_MINIMUM = 5; -function ContactAddressImpl() { } +function stringOrBust(aObj) { + if (typeof aObj != "string") { + if (DEBUG) debug("Field is not a string and was ignored."); + return undefined; + } else { + return aObj; + } +} -ContactAddressImpl.prototype = { - // This function is meant to be called via bindings code for type checking, - // don't call it directly. Instead, create a content object and call initialize - // on that. - initialize: function(aType, aStreetAddress, aLocality, aRegion, aPostalCode, aCountryName, aPref) { - this.type = aType; - this.streetAddress = aStreetAddress; - this.locality = aLocality; - this.region = aRegion; - this.postalCode = aPostalCode; - this.countryName = aCountryName; - this.pref = aPref; - }, +function sanitizeStringArray(aArray) { + if (!Array.isArray(aArray)) { + aArray = [aArray]; + } + return aArray.map(stringOrBust).filter(function(el) { return el != undefined; }); +} - toJSON: function(excludeExposedProps) { - let json = { - type: this.type, - streetAddress: this.streetAddress, - locality: this.locality, - region: this.region, - postalCode: this.postalCode, - countryName: this.countryName, - pref: this.pref, - }; - if (!excludeExposedProps) { - json.__exposedProps__ = { - type: "rw", - streetAddress: "rw", - locality: "rw", - region: "rw", - postalCode: "rw", - countryName: "rw", - pref: "rw", - }; - } - return json; - }, +const nsIClassInfo = Ci.nsIClassInfo; +const CONTACTPROPERTIES_CID = Components.ID("{35ad8a4e-9486-44b6-883d-550f14635e49}"); +const nsIContactProperties = Ci.nsIContactProperties; - classID: Components.ID("{9cbfa81c-bcab-4ca9-b0d2-f4318f295e33}"), - contractID: "@mozilla.org/contactAddress;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), +// ContactProperties is not directly instantiated. It is used as interface. + +function ContactProperties(aProp) { if (DEBUG) debug("ContactProperties Constructor"); } + +ContactProperties.prototype = { + + classID : CONTACTPROPERTIES_CID, + classInfo : XPCOMUtils.generateCI({classID: CONTACTPROPERTIES_CID, + contractID:"@mozilla.org/contactProperties;1", + classDescription: "ContactProperties", + interfaces: [nsIContactProperties], + flags: nsIClassInfo.DOM_OBJECT}), + + QueryInterface : XPCOMUtils.generateQI([nsIContactProperties]) +} + +//ContactAddress + +const CONTACTADDRESS_CONTRACTID = "@mozilla.org/contactAddress;1"; +const CONTACTADDRESS_CID = Components.ID("{9cbfa81c-bcab-4ca9-b0d2-f4318f295e33}"); +const nsIContactAddress = Components.interfaces.nsIContactAddress; + +function ContactAddress(aType, aStreetAddress, aLocality, aRegion, aPostalCode, aCountryName, aPref) { + this.type = sanitizeStringArray(aType); + this.streetAddress = stringOrBust(aStreetAddress); + this.locality = stringOrBust(aLocality); + this.region = stringOrBust(aRegion); + this.postalCode = stringOrBust(aPostalCode); + this.countryName = stringOrBust(aCountryName); + this.pref = aPref; }; -function ContactFieldImpl() { } +ContactAddress.prototype = { + __exposedProps__: { + type: 'rw', + streetAddress: 'rw', + locality: 'rw', + region: 'rw', + postalCode: 'rw', + countryName: 'rw', + pref: 'rw' + }, -ContactFieldImpl.prototype = { - // This function is meant to be called via bindings code for type checking, - // don't call it directly. Instead, create a content object and call initialize - // on that. - initialize: function(aType, aValue, aPref) { - this.type = aType; - this.value = aValue; - this.pref = aPref; - }, + classID : CONTACTADDRESS_CID, + classInfo : XPCOMUtils.generateCI({classID: CONTACTADDRESS_CID, + contractID: CONTACTADDRESS_CONTRACTID, + classDescription: "ContactAddress", + interfaces: [nsIContactAddress], + flags: nsIClassInfo.DOM_OBJECT}), - toJSON: function(excludeExposedProps) { - let json = { - type: this.type, - value: this.value, - pref: this.pref, - }; - if (!excludeExposedProps) { - json.__exposedProps__ = { - type: "rw", - value: "rw", - pref: "rw", - }; - } - return json; - }, + QueryInterface : XPCOMUtils.generateQI([nsIContactAddress]) +} - classID: Components.ID("{ad19a543-69e4-44f0-adfa-37c011556bc1}"), - contractID: "@mozilla.org/contactField;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), +//ContactField + +const CONTACTFIELD_CONTRACTID = "@mozilla.org/contactField;1"; +const CONTACTFIELD_CID = Components.ID("{ad19a543-69e4-44f0-adfa-37c011556bc1}"); +const nsIContactField = Components.interfaces.nsIContactField; + +function ContactField(aType, aValue, aPref) { + this.type = sanitizeStringArray(aType); + this.value = stringOrBust(aValue); + this.pref = aPref; }; -function ContactTelFieldImpl() { } +ContactField.prototype = { + __exposedProps__: { + type: 'rw', + value: 'rw', + pref: 'rw' + }, -ContactTelFieldImpl.prototype = { - // This function is meant to be called via bindings code for type checking, - // don't call it directly. Instead, create a content object and call initialize - // on that. - initialize: function(aType, aValue, aCarrier, aPref) { - this.type = aType; - this.value = aValue; - this.carrier = aCarrier; - this.pref = aPref; - }, + classID : CONTACTFIELD_CID, + classInfo : XPCOMUtils.generateCI({classID: CONTACTFIELD_CID, + contractID: CONTACTFIELD_CONTRACTID, + classDescription: "ContactField", + interfaces: [nsIContactField], + flags: nsIClassInfo.DOM_OBJECT}), - toJSON: function(excludeExposedProps) { - let json = { - type: this.type, - value: this.value, - carrier: this.carrier, - pref: this.pref, - }; - if (!excludeExposedProps) { - json.__exposedProps__ = { - type: "rw", - value: "rw", - carrier: "rw", - pref: "rw", - }; - } - return json; - }, + QueryInterface : XPCOMUtils.generateQI([nsIContactField]) +} - classID: Components.ID("{4d42c5a9-ea5d-4102-80c3-40cc986367ca}"), - contractID: "@mozilla.org/contactTelField;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), +//ContactTelField + +const CONTACTTELFIELD_CONTRACTID = "@mozilla.org/contactTelField;1"; +const CONTACTTELFIELD_CID = Components.ID("{4d42c5a9-ea5d-4102-80c3-40cc986367ca}"); +const nsIContactTelField = Components.interfaces.nsIContactTelField; + +function ContactTelField(aType, aValue, aCarrier, aPref) { + this.type = sanitizeStringArray(aType); + this.value = stringOrBust(aValue); + this.carrier = stringOrBust(aCarrier); + this.pref = aPref; }; +ContactTelField.prototype = { + __exposedProps__: { + type: 'rw', + value: 'rw', + carrier: 'rw', + pref: 'rw' + }, + + classID : CONTACTTELFIELD_CID, + classInfo : XPCOMUtils.generateCI({classID: CONTACTTELFIELD_CID, + contractID: CONTACTTELFIELD_CONTRACTID, + classDescription: "ContactTelField", + interfaces: [nsIContactTelField], + flags: nsIClassInfo.DOM_OBJECT}), + + QueryInterface : XPCOMUtils.generateQI([nsIContactTelField]) +} + +//ContactFindSortOptions + +const CONTACTFINDSORTOPTIONS_CONTRACTID = "@mozilla.org/contactFindSortOptions;1" +const CONTACTFINDSORTOPTIONS_CID = Components.ID("{0a5b1fab-70da-46dd-b902-619904d920c2}"); +const nsIContactFindSortOptions = Ci.nsIContactFindSortOptions; + +function ContactFindSortOptions () { } + +ContactFindSortOptions.prototype = { + classID: CONTACTFINDSORTOPTIONS_CID, + classInfo: XPCOMUtils.generateCI({classID: CONTACTFINDSORTOPTIONS_CID, + contractID: CONTACTFINDSORTOPTIONS_CONTRACTID, + classDescription: "ContactFindSortOptions", + interfaces: [nsIContactFindSortOptions], + flags: nsIClassInfo.DOM_OBJECT}), + QueryInterface: XPCOMUtils.generateQI([nsIContactFindSortOptions]) +}; + +//ContactFindOptions + +const CONTACTFINDOPTIONS_CONTRACTID = "@mozilla.org/contactFindOptions;1"; +const CONTACTFINDOPTIONS_CID = Components.ID("{28ce07d0-45d9-4b7a-8843-521df4edd8bc}"); +const nsIContactFindOptions = Components.interfaces.nsIContactFindOptions; + +function ContactFindOptions() { }; + +ContactFindOptions.prototype = { + + classID : CONTACTFINDOPTIONS_CID, + classInfo : XPCOMUtils.generateCI({classID: CONTACTFINDOPTIONS_CID, + contractID: CONTACTFINDOPTIONS_CONTRACTID, + classDescription: "ContactFindOptions", + interfaces: [nsIContactFindSortOptions, + nsIContactFindOptions], + flags: nsIClassInfo.DOM_OBJECT}), + + QueryInterface : XPCOMUtils.generateQI([nsIContactFindSortOptions, + nsIContactFindOptions]) +} + +//Contact + +const CONTACT_CONTRACTID = "@mozilla.org/contact;1"; +const CONTACT_CID = Components.ID("{72a5ee28-81d8-4af8-90b3-ae935396cc66}"); +const nsIDOMContact = Components.interfaces.nsIDOMContact; + +function checkBlobArray(aBlob) { + if (Array.isArray(aBlob)) { + for (let i = 0; i < aBlob.length; i++) { + if (typeof aBlob != 'object') { + return null; + } + if (!(aBlob[i] instanceof Components.interfaces.nsIDOMBlob)) { + return null; + } + } + return aBlob; + } + return null; +} + +function isVanillaObj(aObj) { + return Object.prototype.toString.call(aObj) == "[object Object]"; +} + function validateArrayField(data, createCb) { - // We use an array-like Proxy to validate data set by content, since we don't - // have WebIDL arrays yet. See bug 851726. - - // ArrayPropertyExposedPropsProxy is used to return "rw" for any valid index - // and "length" in __exposedProps__. - const ArrayPropertyExposedPropsProxy = new Proxy({}, { - get: function(target, name) { - // Test for index access - if (String(name >>> 0) === name) { - return "rw"; - } - if (name === "length") { - return "r"; - } - } - }); - - const ArrayPropertyHandler = { - set: function(target, name, val, receiver) { - // Test for index access - if (String(name >>> 0) === name) { - target[name] = createCb(val); - } - }, - get: function(target, name) { - if (name === "__exposedProps__") { - return ArrayPropertyExposedPropsProxy; - } - return target[name]; - } - }; - if (data) { data = Array.isArray(data) ? data : [data]; let filtered = []; for (let obj of data) { - filtered.push(createCb(obj)); + if (obj && isVanillaObj(obj)) { + filtered.push(createCb(obj)); + } } - if (filtered.length === 0) { - return undefined; - } - return new Proxy(filtered, ArrayPropertyHandler); + return filtered; } return undefined; } -// We need this to create a copy of the mozContact object in ContactManager.save -// Keep in sync with the interfaces. -const PROPERTIES = [ - "name", "honorificPrefix", "givenName", "additionalName", "familyName", - "honorificSuffix", "nickname", "photo", "category", "org", "jobTitle", - "bday", "note", "anniversary", "sex", "genderIdentity", "key" -]; -const ADDRESS_PROPERTIES = ["adr"]; -const FIELD_PROPERTIES = ["email", "url", "impp"]; -const TELFIELD_PROPERTIES = ["tel"]; - -function Contact() { } +function Contact() { }; Contact.prototype = { - // We need to create the content interfaces in these setters, otherwise when - // we return these objects (e.g. from a find call), the values in the array - // will be COW's, and content cannot see the properties. + __exposedProps__: { + id: 'rw', + updated: 'rw', + published: 'rw', + name: 'rw', + honorificPrefix: 'rw', + givenName: 'rw', + additionalName: 'rw', + familyName: 'rw', + honorificSuffix: 'rw', + nickname: 'rw', + email: 'rw', + photo: 'rw', + url: 'rw', + category: 'rw', + adr: 'rw', + tel: 'rw', + org: 'rw', + jobTitle: 'rw', + bday: 'rw', + note: 'rw', + impp: 'rw', + anniversary: 'rw', + sex: 'rw', + genderIdentity: 'rw', + key: 'rw', + }, + + set name(aName) { + this._name = sanitizeStringArray(aName); + }, + + get name() { + return this._name; + }, + + set honorificPrefix(aHonorificPrefix) { + this._honorificPrefix = sanitizeStringArray(aHonorificPrefix); + }, + + get honorificPrefix() { + return this._honorificPrefix; + }, + + set givenName(aGivenName) { + this._givenName = sanitizeStringArray(aGivenName); + }, + + get givenName() { + return this._givenName; + }, + + set additionalName(aAdditionalName) { + this._additionalName = sanitizeStringArray(aAdditionalName); + }, + + get additionalName() { + return this._additionalName; + }, + + set familyName(aFamilyName) { + this._familyName = sanitizeStringArray(aFamilyName); + }, + + get familyName() { + return this._familyName; + }, + + set honorificSuffix(aHonorificSuffix) { + this._honorificSuffix = sanitizeStringArray(aHonorificSuffix); + }, + + get honorificSuffix() { + return this._honorificSuffix; + }, + + set nickname(aNickname) { + this._nickname = sanitizeStringArray(aNickname); + }, + + get nickname() { + return this._nickname; + }, + + set photo(aPhoto) { + this._photo = checkBlobArray(aPhoto); + }, + + get photo() { + return this._photo; + }, + + set category(aCategory) { + this._category = sanitizeStringArray(aCategory); + }, + + get category() { + return this._category; + }, + set email(aEmail) { - this._email = aEmail; + this._email = validateArrayField(aEmail, function(email) { + return new ContactField(email.type, email.value, email.pref); + }); }, get email() { - this._email = validateArrayField(this._email, function(email) { - let obj = this._window.ContactField._create(this._window, new ContactFieldImpl()); - obj.initialize(email.type, email.value, email.pref); - return obj; - }.bind(this)); return this._email; }, set adr(aAdr) { - this._adr = aAdr; + this._adr = validateArrayField(aAdr, function(adr) { + return new ContactAddress(adr.type, adr.streetAddress, adr.locality, + adr.region, adr.postalCode, adr.countryName, + adr.pref); + }); }, get adr() { - this._adr = validateArrayField(this._adr, function(adr) { - let obj = this._window.ContactAddress._create(this._window, new ContactAddressImpl()); - obj.initialize(adr.type, adr.streetAddress, adr.locality, - adr.region, adr.postalCode, adr.countryName, - adr.pref); - return obj; - }.bind(this)); return this._adr; }, set tel(aTel) { - this._tel = aTel; + this._tel = validateArrayField(aTel, function(tel) { + return new ContactTelField(tel.type, tel.value, tel.carrier, tel.pref); + }); }, get tel() { - this._tel = validateArrayField(this._tel, function(tel) { - let obj = this._window.ContactTelField._create(this._window, new ContactTelFieldImpl()); - obj.initialize(tel.type, tel.value, tel.carrier, tel.pref); - return obj; - }.bind(this)); return this._tel; }, set impp(aImpp) { - this._impp = aImpp; + this._impp = validateArrayField(aImpp, function(impp) { + return new ContactField(impp.type, impp.value, impp.pref); + }); }, get impp() { - this._impp = validateArrayField(this._impp, function(impp) { - let obj = this._window.ContactField._create(this._window, new ContactFieldImpl()); - obj.initialize(impp.type, impp.value, impp.pref); - return obj; - }.bind(this)); return this._impp; }, set url(aUrl) { - this._url = aUrl; + this._url = validateArrayField(aUrl, function(url) { + return new ContactField(url.type, url.value, url.pref); + }); }, get url() { - this._url = validateArrayField(this._url, function(url) { - let obj = this._window.ContactField._create(this._window, new ContactFieldImpl()); - obj.initialize(url.type, url.value, url.pref); - return obj; - }.bind(this)); return this._url; }, - init: function(aWindow) { - this._window = aWindow; + set org(aOrg) { + this._org = sanitizeStringArray(aOrg); }, - __init: function(aProp) { + get org() { + return this._org; + }, + + set jobTitle(aJobTitle) { + this._jobTitle = sanitizeStringArray(aJobTitle); + }, + + get jobTitle() { + return this._jobTitle; + }, + + set note(aNote) { + this._note = sanitizeStringArray(aNote); + }, + + get note() { + return this._note; + }, + + set bday(aBday) { + if (aBday && aBday.constructor.name === "Date") { + this._bday = aBday; + } else if (typeof aBday === "string" || typeof aBday === "number") { + this._bday = new Date(aBday); + } + }, + + get bday() { + return this._bday; + }, + + set anniversary(aAnniversary) { + if (aAnniversary && aAnniversary.constructor.name === "Date") { + this._anniversary = aAnniversary; + } else if (typeof aAnniversary === "string" || typeof aAnniversary === "number") { + this._anniversary = new Date(aAnniversary); + } + }, + + get anniversary() { + return this._anniversary; + }, + + set sex(aSex) { + if (aSex !== "undefined") { + this._sex = aSex; + } else { + this._sex = null; + } + }, + + get sex() { + return this._sex; + }, + + set genderIdentity(aGenderIdentity) { + if (aGenderIdentity !== "undefined") { + this._genderIdentity = aGenderIdentity; + } else { + this._genderIdentity = null; + } + }, + + get genderIdentity() { + return this._genderIdentity; + }, + + set key(aKey) { + this._key = sanitizeStringArray(aKey); + }, + + get key() { + return this._key; + }, + + init: function init(aProp) { this.name = aProp.name; this.honorificPrefix = aProp.honorificPrefix; this.givenName = aProp.givenName; @@ -303,99 +501,76 @@ Contact.prototype = { this.key = aProp.key; }, - setMetadata: function(aId, aPublished, aUpdated) { - this.id = aId; - if (aPublished) { - this.published = aPublished; - } - if (aUpdated) { - this.updated = aUpdated; - } + get published () { + return this._published; }, - toJSON: function() { - return { - id: this.id, - published: this.published, - updated: this.updated, - - name: this.name, - honorificPrefix: this.honorificPrefix, - givenName: this.givenName, - additionalName: this.additionalName, - familyName: this.familyName, - honorificSuffix: this.honorificSuffix, - nickname: this.nickname, - category: this.category, - org: this.org, - jobTitle: this.jobTitle, - note: this.note, - sex: this.sex, - genderIdentity: this.genderIdentity, - email: this.email, - photo: this.photo, - adr: this.adr, - url: this.url, - tel: this.tel, - bday: this.bday, - impp: this.impp, - anniversary: this.anniversary, - key: this.key, - - __exposedProps__: { - id: "rw", - published: "rw", - updated: "rw", - name: "rw", - honorificPrefix: "rw", - givenName: "rw", - additionalName: "rw", - familyName: "rw", - honorificSuffix: "rw", - nickname: "rw", - category: "rw", - org: "rw", - jobTitle: "rw", - note: "rw", - sex: "rw", - genderIdentity: "rw", - email: "rw", - photo: "rw", - adr: "rw", - url: "rw", - tel: "rw", - bday: "rw", - impp: "rw", - anniversary: "rw", - key: "rw", - } - }; + set published(aPublished) { + this._published = aPublished; }, - classID: Components.ID("{72a5ee28-81d8-4af8-90b3-ae935396cc66}"), - contractID: "@mozilla.org/contact;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, - Ci.nsIDOMGlobalPropertyInitializer]), -}; + get updated () { + return this._updated; + }, -function ContactManager() { } + set updated(aUpdated) { + this._updated = aUpdated; + }, + + classID : CONTACT_CID, + classInfo : XPCOMUtils.generateCI({classID: CONTACT_CID, + contractID: CONTACT_CONTRACTID, + classDescription: "Contact", + interfaces: [nsIDOMContact, nsIContactProperties], + flags: nsIClassInfo.DOM_OBJECT}), + + QueryInterface : XPCOMUtils.generateQI([nsIDOMContact, nsIContactProperties]) +} + +// ContactManager + +const CONTACTMANAGER_CONTRACTID = "@mozilla.org/contactManager;1"; +const CONTACTMANAGER_CID = Components.ID("{8beb3a66-d70a-4111-b216-b8e995ad3aff}"); +const nsIDOMContactManager = Components.interfaces.nsIDOMContactManager; + +function ContactManager() +{ + if (DEBUG) debug("Constructor"); +} ContactManager.prototype = { __proto__: DOMRequestIpcHelper.prototype, - hasListenPermission: false, + _oncontactchange: null, _cachedContacts: [] , - set oncontactchange(aHandler) { - this.__DOM_IMPL__.setEventHandler("oncontactchange", aHandler); + set oncontactchange(aCallback) { + if (DEBUG) debug("set oncontactchange"); + let allowCallback = function() { + if (!this._oncontactchange) { + cpmm.sendAsyncMessage("Contacts:RegisterForMessages"); + } + this._oncontactchange = aCallback; + }.bind(this); + let cancelCallback = function() { + throw Components.results.NS_ERROR_FAILURE; + } + this.askPermission("listen", null, allowCallback, cancelCallback); }, get oncontactchange() { - return this.__DOM_IMPL__.getEventHandler("oncontactchange"); + return this._oncontactchange; }, - _convertContact: function(aContact) { - let newContact = new this._window.mozContact(aContact.properties); - newContact.setMetadata(aContact.id, aContact.published, aContact.updated); + _setMetaData: function(aNewContact, aRecord) { + aNewContact.id = aRecord.id; + aNewContact.published = aRecord.published; + aNewContact.updated = aRecord.updated; + }, + + _convertContact: function CM_convertContact(aContact) { + let newContact = new Contact(); + newContact.init(aContact.properties); + this._setMetaData(newContact, aContact); return newContact; }, @@ -505,11 +680,13 @@ ContactManager.prototype = { case "Contact:Changed": // Fire oncontactchange event if (DEBUG) debug("Contacts:ContactChanged: " + msg.contactID + ", " + msg.reason); - let event = new this._window.MozContactChangeEvent("contactchange", { - contactID: msg.contactID, - reason: msg.reason - }); - this.dispatchEvent(event); + if (this._oncontactchange) { + let event = new this._window.MozContactChangeEvent("contactchanged", { + contactID: msg.contactID, + reason: msg.reason + }); + this._oncontactchange.handleEvent(event); + } break; case "Contacts:Revision": if (DEBUG) debug("new revision: " + msg.revision); @@ -531,12 +708,6 @@ ContactManager.prototype = { this.removeRequest(msg.requestID); }, - dispatchEvent: function(event) { - if (this.hasListenPermission) { - this.__DOM_IMPL__.dispatchEvent(event); - } - }, - askPermission: function (aAccess, aRequest, aAllowCallback, aCancelCallback) { if (DEBUG) debug("askPermission for contacts"); let access; @@ -594,53 +765,35 @@ ContactManager.prototype = { }, save: function save(aContact) { - // We have to do a deep copy of the contact manually here because - // nsFrameMessageManager doesn't know how to create a structured clone of a - // mozContact object. - let newContact = {properties: {}}; - - for (let field of PROPERTIES) { - if (aContact[field]) { - newContact.properties[field] = aContact[field]; - } + if (DEBUG) debug("save: " + JSON.stringify(aContact) + " :" + aContact.id); + let newContact = {}; + newContact.properties = { + name: [], + honorificPrefix: [], + givenName: [], + additionalName: [], + familyName: [], + honorificSuffix: [], + nickname: [], + email: [], + photo: [], + url: [], + category: [], + adr: [], + tel: [], + org: [], + jobTitle: [], + bday: null, + note: [], + impp: [], + anniversary: null, + sex: null, + genderIdentity: null, + key: [], + }; + for (let field in newContact.properties) { + newContact.properties[field] = aContact[field]; } - - for (let prop of ADDRESS_PROPERTIES) { - if (aContact[prop]) { - newContact.properties[prop] = []; - for (let i of aContact[prop]) { - if (i) { - let json = ContactAddressImpl.prototype.toJSON.apply(i, [true]); - newContact.properties[prop].push(json); - } - } - } - } - - for (let prop of FIELD_PROPERTIES) { - if (aContact[prop]) { - newContact.properties[prop] = []; - for (let i of aContact[prop]) { - if (i) { - let json = ContactFieldImpl.prototype.toJSON.apply(i, [true]); - newContact.properties[prop].push(json); - } - } - } - } - - for (let prop of TELFIELD_PROPERTIES) { - if (aContact[prop]) { - newContact.properties[prop] = []; - for (let i of aContact[prop]) { - if (i) { - let json = ContactTelFieldImpl.prototype.toJSON.apply(i, [true]); - newContact.properties[prop].push(json); - } - } - } - } - let request = this.createRequest(); let requestID = this.getRequestId({request: request, reason: reason}); @@ -648,7 +801,7 @@ ContactManager.prototype = { if (aContact.id == "undefined") { // for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes // 25c00f0190e5c545b4d421E2ddbab9e0 - aContact.id = this._getRandomId().replace(/[{}-]/g, ""); + aContact.id = this._getRandomId().replace('-', '', 'g').replace('{', '').replace('}', ''); // Cache the contact so that its ID may be updated later if necessary this._cachedContacts[requestID] = aContact; reason = "create"; @@ -656,12 +809,8 @@ ContactManager.prototype = { reason = "update"; } - newContact.id = aContact.id; - newContact.published = aContact.published; - newContact.updated = aContact.updated; - + this._setMetaData(newContact, aContact); if (DEBUG) debug("send: " + JSON.stringify(newContact)); - let options = { contact: newContact, reason: reason }; let allowCallback = function() { cpmm.sendAsyncMessage("Contact:Save", {requestID: requestID, options: options}); @@ -737,18 +886,19 @@ ContactManager.prototype = { let options = { id: aRecord.id }; let allowCallback = function() { cpmm.sendAsyncMessage("Contact:Remove", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options}); - }.bind(this); + }.bind(this) this.askPermission("remove", request, allowCallback); return request; }, clear: function() { if (DEBUG) debug("clear"); - let request = this.createRequest(); + let request; + request = this.createRequest(); let options = {}; let allowCallback = function() { cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options}); - }.bind(this); + }.bind(this) this.askPermission("remove", request, allowCallback); return request; }, @@ -788,7 +938,6 @@ ContactManager.prototype = { }, init: function(aWindow) { - // DOMRequestIpcHelper.initHelper sets this._window this.initDOMRequestHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO", "Contacts:Clear:Return:OK", "Contacts:Clear:Return:KO", "Contact:Save:Return:OK", "Contact:Save:Return:KO", @@ -798,22 +947,26 @@ ContactManager.prototype = { "Contacts:GetAll:Next", "Contacts:GetAll:Return:KO", "Contacts:Count", "Contacts:Revision", "Contacts:GetRevision:Return:KO",]); - - - let allowCallback = function() { - cpmm.sendAsyncMessage("Contacts:RegisterForMessages"); - this.hasListenPermission = true; - }.bind(this); - - this.askPermission("listen", null, allowCallback); }, - classID: Components.ID("{8beb3a66-d70a-4111-b216-b8e995ad3aff}"), - contractID: "@mozilla.org/contactManager;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, - Ci.nsIDOMGlobalPropertyInitializer]), -}; + // Called from DOMRequestIpcHelper + uninit: function uninit() { + if (DEBUG) debug("uninit call"); + if (this._oncontactchange) + this._oncontactchange = null; + }, -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ - Contact, ContactManager, ContactFieldImpl, ContactAddressImpl, ContactTelFieldImpl -]); + classID : CONTACTMANAGER_CID, + QueryInterface : XPCOMUtils.generateQI([nsIDOMContactManager, + Ci.nsIDOMGlobalPropertyInitializer, + Ci.nsISupportsWeakReference]), + + classInfo : XPCOMUtils.generateCI({classID: CONTACTMANAGER_CID, + contractID: CONTACTMANAGER_CONTRACTID, + classDescription: "ContactManager", + interfaces: [nsIDOMContactManager], + flags: nsIClassInfo.DOM_OBJECT}) +} + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory( + [Contact, ContactManager, ContactProperties, ContactAddress, ContactField, ContactTelField, ContactFindSortOptions, ContactFindOptions]) diff --git a/dom/contacts/ContactManager.manifest b/dom/contacts/ContactManager.manifest index a0938fe90eba..20484b625633 100644 --- a/dom/contacts/ContactManager.manifest +++ b/dom/contacts/ContactManager.manifest @@ -1,3 +1,6 @@ +component {35ad8a4e-9486-44b6-883d-550f14635e49} ContactManager.js +contract @mozilla.org/contactProperties;1 {35ad8a4e-9486-44b6-883d-550f14635e49} + component {9cbfa81c-bcab-4ca9-b0d2-f4318f295e33} ContactManager.js contract @mozilla.org/contactAddress;1 {9cbfa81c-bcab-4ca9-b0d2-f4318f295e33} @@ -7,8 +10,16 @@ contract @mozilla.org/contactField;1 {ad19a543-69e4-44f0-adfa-37c011556bc1} component {4d42c5a9-ea5d-4102-80c3-40cc986367ca} ContactManager.js contract @mozilla.org/contactTelField;1 {4d42c5a9-ea5d-4102-80c3-40cc986367ca} +component {0a5b1fab-70da-46dd-b902-619904d920c2} ContactManager.js +contract @mozilla.org/contactFindSortOptions;1 {0a5b1fab-70da-46dd-b902-619904d920c2} + +component {28ce07d0-45d9-4b7a-8843-521df4edd8bc} ContactManager.js +contract @mozilla.org/contactFindOptions;1 {28ce07d0-45d9-4b7a-8843-521df4edd8bc} + component {72a5ee28-81d8-4af8-90b3-ae935396cc66} ContactManager.js contract @mozilla.org/contact;1 {72a5ee28-81d8-4af8-90b3-ae935396cc66} +category JavaScript-global-constructor mozContact @mozilla.org/contact;1 component {8beb3a66-d70a-4111-b216-b8e995ad3aff} ContactManager.js contract @mozilla.org/contactManager;1 {8beb3a66-d70a-4111-b216-b8e995ad3aff} +category JavaScript-navigator-property mozContacts @mozilla.org/contactManager;1 diff --git a/dom/contacts/fallback/ContactDB.jsm b/dom/contacts/fallback/ContactDB.jsm index cc77fec14742..793772f8bffa 100644 --- a/dom/contacts/fallback/ContactDB.jsm +++ b/dom/contacts/fallback/ContactDB.jsm @@ -20,28 +20,24 @@ Cu.import("resource://gre/modules/IndexedDBHelper.jsm"); Cu.import("resource://gre/modules/PhoneNumberUtils.jsm"); const DB_NAME = "contacts"; -const DB_VERSION = 15; +const DB_VERSION = 14; const STORE_NAME = "contacts"; const SAVED_GETALL_STORE_NAME = "getallcache"; const CHUNK_SIZE = 20; const REVISION_STORE = "revision"; const REVISION_KEY = "revision"; -function optionalDate(aValue) { - if (aValue) { - if (!(aValue instanceof Date)) { - return new Date(aValue); - } - return aValue; - } - return undefined; -} - function exportContact(aRecord) { - if (aRecord) { - delete aRecord.search; - } - return aRecord; + let contact = {}; + contact.properties = aRecord.properties; + + for (let field in aRecord.properties) + contact.properties[field] = aRecord.properties[field]; + + contact.updated = aRecord.updated; + contact.published = aRecord.published; + contact.id = aRecord.id; + return contact; } function ContactDispatcher(aContacts, aFullContacts, aCallback, aNewTxn, aClearDispatcher, aFailureCb) { @@ -154,7 +150,9 @@ ContactDB.prototype = { for (let i = 0; i < contacts.length; i++) { let contact = {}; contact.properties = contacts[i]; - contact.id = idService.generateUUID().toString().replace(/[{}-]/g, ""); + contact.id = idService.generateUUID().toString().replace('-', '', 'g') + .replace('{', '') + .replace('}', ''); contact = this.makeImport(contact); this.updateRecordMetadata(contact); if (DEBUG) debug("import: " + JSON.stringify(contact)); @@ -551,47 +549,6 @@ ContactDB.prototype = { } }; }, - function upgrade14to15() { - if (DEBUG) debug("Fix array properties saved as scalars"); - if (!objectStore) { - objectStore = aTransaction.objectStore(STORE_NAME); - } - const ARRAY_PROPERTIES = ["photo", "adr", "email", "url", "impp", "tel", - "name", "honorificPrefix", "givenName", - "additionalName", "familyName", "honorificSuffix", - "nickname", "category", "org", "jobTitle", - "note", "key"]; - const PROPERTIES_WITH_TYPE = ["adr", "email", "url", "impp", "tel"]; - objectStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - let changed = false; - if (cursor) { - let props = cursor.value.properties; - for (let prop of ARRAY_PROPERTIES) { - if (props[prop]) { - if (!Array.isArray(props[prop])) { - cursor.value.properties[prop] = [props[prop]]; - changed = true; - } - if (PROPERTIES_WITH_TYPE.indexOf(prop) !== -1) { - for (let subprop of cursor.value.properties[prop]) { - if (!Array.isArray(subprop.type)) { - subprop.type = [subprop.type]; - changed = true; - } - } - } - } - } - if (changed) { - cursor.update(cursor.value); - } - cursor.continue(); - } else { - next(); - } - }; - }, ]; let index = aOldVersion; @@ -619,7 +576,31 @@ ContactDB.prototype = { }, makeImport: function makeImport(aContact) { - let contact = {properties: {}}; + let contact = {}; + contact.properties = { + name: [], + honorificPrefix: [], + givenName: [], + additionalName: [], + familyName: [], + honorificSuffix: [], + nickname: [], + email: [], + photo: [], + url: [], + category: [], + adr: [], + tel: [], + org: [], + jobTitle: [], + bday: null, + note: [], + impp: [], + anniversary: null, + sex: null, + genderIdentity: null, + key: [], + }; contact.search = { givenName: [], @@ -706,9 +687,10 @@ ContactDB.prototype = { } } } + if (DEBUG) debug("contact:" + JSON.stringify(contact)); - contact.updated = optionalDate(aContact.updated); - contact.published = optionalDate(aContact.published); + contact.updated = aContact.updated; + contact.published = aContact.published; contact.id = aContact.id; return contact; diff --git a/dom/contacts/tests/test_contacts_basics.html b/dom/contacts/tests/test_contacts_basics.html index 04fca7806cc5..662e87424c89 100644 --- a/dom/contacts/tests/test_contacts_basics.html +++ b/dom/contacts/tests/test_contacts_basics.html @@ -61,30 +61,29 @@ var c4 = { }; var c5 = { - familyName: [], - givenName: [], + nickname: "empty" }; var c6 = { - name: ["e"], + name: "e", familyName: ["e","e","e"], givenName: ["e","e","e"], }; var c7 = { - name: ["e"], + name: "e", familyName: ["e","e","e"], givenName: ["e","e","e"], }; var c8 = { - name: ["e"], + name: "e", familyName: ["e","e","e"], givenName: ["e","e","e"], }; var adr1 = { - type: ["work"], + type: "work", streetAddress: "street 1", locality: "locality 1", region: "region 1", @@ -93,7 +92,7 @@ var adr1 = { }; var adr2 = { - type: ["home, fax"], + type: "home, fax", streetAddress: "street2", locality: "locality2", region: "region2", @@ -105,34 +104,34 @@ var properties1 = { name: ["Test1 TestFamilyName", "Test2 Wagner"], familyName: ["TestFamilyName","Wagner"], givenName: ["Test1","Test2"], - nickname: ["nicktest"], + nickname: "nicktest", tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+55 (31) 9876-3456"}, {type: ["home"], value: "+49 451 491934"}], - adr: [adr1], + adr: adr1, email: [{type: ["work"], value: "x@y.com"}], }; var properties2 = { name: ["dummyHonorificPrefix dummyGivenName dummyFamilyName dummyHonorificSuffix", "dummyHonorificPrefix2"], - familyName: ["dummyFamilyName"], - givenName: ["dummyGivenName"], + familyName: "dummyFamilyName", + givenName: "dummyGivenName", honorificPrefix: ["dummyHonorificPrefix","dummyHonorificPrefix2"], - honorificSuffix: ["dummyHonorificSuffix"], - additionalName: ["dummyadditionalName"], - nickname: ["dummyNickname"], + honorificSuffix: "dummyHonorificSuffix", + additionalName: "dummyadditionalName", + nickname: "dummyNickname", tel: [{type: ["test"], value: "7932012345", carrier: "myCarrier", pref: 1},{type: ["home", "custom"], value: "7932012346", pref: 0}], email: [{type: ["test"], value: "a@b.c"}, {value: "b@c.d", pref: 1}], adr: [adr1, adr2], impp: [{type: ["aim"], value:"im1", pref: 1}, {value: "im2"}], org: ["org1", "org2"], jobTitle: ["boss", "superboss"], - note: ["test note"], + note: "test note", category: ["cat1", "cat2"], url: [{type: ["work", "work2"], value: "www.1.com", pref: 1}, {value:"www2.com"}], bday: new Date("1980, 12, 01"), anniversary: new Date("2000, 12, 01"), sex: "male", genderIdentity: "test", - key: ["ERPJ394GJJWEVJ0349GJ09W3H4FG0WFW80VHW3408GH30WGH348G3H"] + key: "ERPJ394GJJWEVJ0349GJ09W3H4FG0WFW80VHW3408GH30WGH348G3H" }; var sample_id1; @@ -167,39 +166,20 @@ function checkStr(str1, str2, msg) { ok(false, "Expected both strings to be either present or absent"); return; } - if (!str1 || str1 == "null") { - str1 = null; - } - if (!str2 || str2 == "null") { - str2 = null; - } is(str1, str2, msg); } function checkStrArray(str1, str2, msg) { - function normalize_falsy(v) { - if (!v || v == "null" || v == "undefined") { - return ""; - } - return v; + // comparing /[null(,null)+]/ and undefined should pass + function nonNull(e) { + return e != null; } - function optArray(val) { - return Array.isArray(val) ? val : [val]; + if ((Array.isArray(str1) && str1.filter(nonNull).length == 0 && str2 == undefined) + ||(Array.isArray(str2) && str2.filter(nonNull).length == 0 && str1 == undefined)) { + ok(true, msg); + } else if (str1) { + is(JSON.stringify(typeof str1 == "string" ? [str1] : str1), JSON.stringify(typeof str2 == "string" ? [str2] : str2), msg); } - str1 = optArray(str1).map(normalize_falsy).filter(v => v != ""); - str2 = optArray(str2).map(normalize_falsy).filter(v => v != ""); - ise(JSON.stringify(str1), JSON.stringify(str2), msg); -} - -function checkPref(pref1, pref2) { - // If on Android treat one preference as 0 and the other as undefined as matching - if (isAndroid) { - if ((!pref1 && pref2 == undefined) || (pref1 == undefined && !pref2)) { - pref1 = false; - pref2 = false; - } - } - ise(!!pref1, !!pref2, "Same pref"); } function checkAddress(adr1, adr2) { @@ -208,31 +188,44 @@ function checkAddress(adr1, adr2) { return; } checkStrArray(adr1.type, adr2.type, "Same type"); - checkStr(adr1.streetAddress, adr2.streetAddress, "Same streetAddress"); - checkStr(adr1.locality, adr2.locality, "Same locality"); - checkStr(adr1.region, adr2.region, "Same region"); - checkStr(adr1.postalCode, adr2.postalCode, "Same postalCode"); - checkStr(adr1.countryName, adr2.countryName, "Same countryName"); + checkStrArray(adr1.streetAddress, adr2.streetAddress, "Same streetAddress"); + checkStrArray(adr1.locality, adr2.locality, "Same locality"); + checkStrArray(adr1.region, adr2.region, "Same region"); + checkStrArray(adr1.postalCode, adr2.postalCode, "Same postalCode"); + checkStrArray(adr1.countryName, adr2.countryName, "Same countryName"); checkPref(adr1.pref, adr2.pref); } +function checkTel(tel1, tel2) { + if (tel1 ^ tel2) { + ok(false, "Expected both tels to be either present or absent"); + return; + } + checkStrArray(tel1.type, tel2.type, "Same type"); + checkStrArray(tel1.value, tel2.value, "Same value"); + checkStrArray(tel1.carrier, tel2.carrier, "Same carrier"); + checkPref(tel1.pref, tel2.pref); +} + function checkField(field1, field2) { if (field1 ^ field2) { ok(false, "Expected both fields to be either present or absent"); return; } checkStrArray(field1.type, field2.type, "Same type"); - checkStr(field1.value, field2.value, "Same value"); + checkStrArray(field1.value, field2.value, "Same value"); checkPref(field1.pref, field2.pref); } -function checkTel(tel1, tel2) { - if (tel1 ^ tel2) { - ok(false, "Expected both tels to be either present or absent"); - return; +function checkPref(pref1, pref2) { + // If on Android treat one preference as 0 and the other as undefined as matching + if (isAndroid) { + if ((pref1 == 0 && pref2 == undefined) || (pref1 == undefined && pref2 == 0)) { + pref1 = 0; + pref2 = 0; + } } - checkField(tel1, tel2); - checkStr(tel1.carrier, tel2.carrier, "Same carrier"); + is(pref1, pref2, "Same pref"); } function checkCategory(category1, category2) { @@ -246,42 +239,21 @@ function checkCategory(category1, category2) { } function removeAndroidDefaultCategory(category) { - if (!category) { - return category; + if (category == undefined) { + return; } - var result = []; - - for (var i of category) { + for (var i = 0; i < category.length; i++) { // Some devices may return the full group name (prefixed with "System Group: ") - if (i != "My Contacts" && i != "System Group: My Contacts") { - result.push(i); + if (category[i] == "My Contacts" || category[i] == "System Group: My Contacts") { + category.splice(i, 1); } } - return result; -} - -function checkArrayField(array1, array2, func, msg) { - if (!!array1 ^ !!array2) { - ok(false, "Expected both arrays to be either present or absent"); - return; - } - if (!array1 && !array2) { - ok(true, msg); - return; - } - ise(array1.length, array2.length, "Same length"); - for (var i = 0; i < array1.length; ++i) { - func(array1[i], array2[i], msg); - } + return category; } function checkContacts(contact1, contact2) { - if (!!contact1 ^ !!contact2) { - ok(false, "Expected both contacts to be either present or absent"); - return; - } checkStrArray(contact1.name, contact2.name, "Same name"); checkStrArray(contact1.honorificPrefix, contact2.honorificPrefix, "Same honorificPrefix"); checkStrArray(contact1.givenName, contact2.givenName, "Same givenName"); @@ -299,11 +271,21 @@ function checkContacts(contact1, contact2) { checkStr(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity"); checkStrArray(contact1.key, contact2.key, "Same key"); - checkArrayField(contact1.adr, contact2.adr, checkAddress, "Same adr"); - checkArrayField(contact1.tel, contact2.tel, checkTel, "Same tel"); - checkArrayField(contact1.email, contact2.email, checkField, "Same email"); - checkArrayField(contact1.url, contact2.url, checkField, "Same url"); - checkArrayField(contact1.impp, contact2.impp, checkField, "Same impp"); + for (var i in contact1.email) { + checkField(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) { + checkTel(contact1.tel[i], contact2.tel[i]); + } + for (var i in contact1.url) { + checkField(contact1.url[i], contact2.url[i]); + } + for (var i in contact1.impp) { + checkField(contact1.impp[i], contact2.impp[i]); + } } var req; @@ -311,10 +293,6 @@ var index = 0; var initialRev; -var defaultOptions = { - sortBy: "givenName", -}; - function checkRevision(revision, msg, then) { var revReq = mozContacts.getRevision(); revReq.onsuccess = function(e) { @@ -341,6 +319,8 @@ function checkCount(count, msg, then) { } var mozContacts = window.navigator.mozContacts; +ok(mozContacts, "mozContacts exists"); +ok("mozContact" in window, "mozContact exists"); var steps = [ function() { req = mozContacts.getRevision(); @@ -374,7 +354,7 @@ var steps = [ }, function () { ok(true, "Retrieving all contacts"); - req = mozContacts.find(defaultOptions); + req = mozContacts.find({}); req.onsuccess = function () { is(req.result.length, 0, "Empty database."); checkRevision(1, "Revision was not incremented on find", next); @@ -383,7 +363,8 @@ var steps = [ }, function () { ok(true, "Adding empty contact"); - createResult1 = new mozContact({}); + createResult1 = new mozContact(); + createResult1.init({}); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -396,7 +377,7 @@ var steps = [ }, function () { ok(true, "Retrieving all contacts"); - req = mozContacts.find(defaultOptions); + req = mozContacts.find({}); req.onsuccess = function () { is(req.result.length, 1, "One contact."); findResult1 = req.result[0]; @@ -408,7 +389,7 @@ var steps = [ ok(true, "Deleting empty contact"); req = navigator.mozContacts.remove(findResult1); req.onsuccess = function () { - var req2 = mozContacts.find(defaultOptions); + var req2 = mozContacts.find({}); req2.onsuccess = function () { is(req2.result.length, 0, "Empty Database."); clearTemps(); @@ -420,7 +401,8 @@ var steps = [ }, function () { ok(true, "Adding a new contact1"); - createResult1 = new mozContact(properties1); + createResult1 = new mozContact(); + createResult1.init(properties1); mozContacts.oncontactchange = function(event) { is(event.contactID, createResult1.id, "Same contactID"); @@ -505,7 +487,8 @@ var steps = [ is(event.contactID, createResult2.id, "Same contactID"); is(event.reason, "create", "Same reason"); } - createResult2 = new mozContact({name: ["newName"]}); + createResult2 = new mozContact(); + createResult2.init({name: "newName"}); req = navigator.mozContacts.save(createResult2); req.onsuccess = function () { ok(createResult2.id, "The contact now has an ID."); @@ -590,7 +573,8 @@ var steps = [ }, function () { ok(true, "Adding a new contact with properties1"); - createResult1 = new mozContact(properties1); + createResult1 = new mozContact(); + createResult1.init(properties1); mozContacts.oncontactchange = null; req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { @@ -804,16 +788,14 @@ var steps = [ }, function () { ok(true, "Retrieving all contacts"); - req = mozContacts.find(defaultOptions); + req = mozContacts.find({}); req.onsuccess = function() { is(req.result.length, 1, "Found exactly 1 contact."); findResult1 = req.result[0]; ok(findResult1.id == sample_id1, "Same ID"); checkContacts(createResult1, findResult1); - if (!isAndroid) { - ok(findResult1.updated, "Has updated field"); - ok(findResult1.published, "Has published field"); - } + ok(findResult1.updated, "Has updated field"); + ok(findResult1.published, "Has published field"); next(); } req.onerror = onFailure; @@ -826,7 +808,7 @@ var steps = [ findResult1.impp = properties1.impp = [{value:"phil impp"}]; req = navigator.mozContacts.save(findResult1); req.onsuccess = function () { - var req2 = mozContacts.find(defaultOptions); + var req2 = mozContacts.find({}); req2.onsuccess = function() { is(req2.result.length, 1, "Found exactly 1 contact."); findResult2 = req2.result[0]; @@ -890,7 +872,7 @@ var steps = [ findResult1.impp = properties1.impp = [{value: "phil impp"}]; req = mozContacts.save(findResult1); req.onsuccess = function () { - var req2 = mozContacts.find(defaultOptions); + var req2 = mozContacts.find({}); req2.onsuccess = function () { is(req2.result.length, 1, "Found exactly 1 contact."); findResult1 = req2.result[0]; @@ -955,8 +937,9 @@ var steps = [ SpecialPowers.executeSoon(next); } else { findResult1.email = [{value: properties1.nickname}]; - findResult1.nickname = ["TEST"]; - var newContact = new mozContact(findResult1); + findResult1.nickname = "TEST"; + var newContact = new mozContact(); + newContact.init(findResult1); req = mozContacts.save(newContact); req.onsuccess = function () { var options = {filterBy: ["email", "givenName"], @@ -978,7 +961,7 @@ var steps = [ ok(true, "Deleting contact" + findResult1); req = mozContacts.remove(findResult1); req.onsuccess = function () { - var req2 = mozContacts.find(defaultOptions); + var req2 = mozContacts.find({}); req2.onsuccess = function () { is(req2.result.length, 1, "One contact left."); findResult1 = req2.result[0]; @@ -999,7 +982,8 @@ var steps = [ }, function () { ok(true, "Adding a new contact"); - createResult1 = new mozContact(properties1); + createResult1 = new mozContact(); + createResult1.init(properties1); req = mozContacts.save(createResult1) req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1010,7 +994,8 @@ var steps = [ }, function () { ok(true, "Adding a new contact2"); - createResult2 = new mozContact(properties2); + createResult2 = new mozContact(); + createResult2.init(properties2); req = mozContacts.save(createResult2); req.onsuccess = function () { ok(createResult2.id, "The contact now has an ID."); @@ -1021,10 +1006,10 @@ var steps = [ }, function () { ok(true, "Retrieving all contacts"); - req = mozContacts.find({sortBy: "familyName"}); + req = mozContacts.find({sortBy: 'FamilyName',}) req.onsuccess = function () { is(req.result.length, 2, "Found exactly 2 contact."); - checkContacts(req.result[1], properties1); + checkContacts(properties2, req.result[1]); next(); } req.onerror = onFailure; @@ -1101,14 +1086,16 @@ var steps = [ function () { ok(true, "Adding 20 contacts"); for (var i=0; i<19; i++) { - createResult1 = new mozContact(properties1); + createResult1 = new mozContact(); + createResult1.init(properties1); req = mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); }; req.onerror = onFailure; }; - createResult1 = new mozContact(properties1); + createResult1 = new mozContact(); + createResult1.init(properties1); req = mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1119,7 +1106,7 @@ var steps = [ }, function () { ok(true, "Retrieving all contacts"); - req = mozContacts.find(defaultOptions); + req = mozContacts.find({}); req.onsuccess = function () { is(req.result.length, 20, "20 Entries."); next(); @@ -1165,10 +1152,11 @@ var steps = [ ok(true, "Retrieving all contacts3"); var options = {filterBy: ["givenName", "tel", "email"], filterOp: "startsWith", - filterValue: properties1.givenName[0].substring(0, 4)}; + filterValue: properties1.givenName[0].substring(0, 4), + filterLimit: 15 }; req = mozContacts.find(options); req.onsuccess = function () { - is(req.result.length, 20, "20 Entries."); + is(req.result.length, 15, "15 Entries."); checkContacts(createResult1, req.result[10]); next(); } @@ -1185,7 +1173,8 @@ var steps = [ }, function () { ok(true, "Testing clone contact"); - createResult1 = new mozContact(properties1); + createResult1 = new mozContact(); + createResult1.init(properties1); req = mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1198,14 +1187,14 @@ var steps = [ ok(true, "Testing clone contact2"); var cloned = new mozContact(createResult1); ok(cloned.id != createResult1.id, "Cloned contact has new ID"); - cloned.email = [{value: "new email!"}]; - cloned.givenName = ["Tom"]; + cloned.email = {value: "new email!"}; + cloned.givenName = "Tom"; req = mozContacts.save(cloned); req.onsuccess = function () { ok(cloned.id, "The contact now has an ID."); - is(cloned.email[0].value, "new email!", "Same Email"); - isnot(createResult1.email[0].value, cloned.email[0].value, "Clone has different email"); - is(cloned.givenName, "Tom", "New Name"); + ok(cloned.email[0].value == "new email!", "Same Email"); + ok(createResult1.email != cloned.email, "Clone has different email"); + ok(cloned.givenName == "Tom", "New Name"); next(); } req.onerror = onFailure; @@ -1215,7 +1204,7 @@ var steps = [ var options = {filterBy: ["givenName"], filterOp: "startsWith", filterValue: properties2.givenName[0].substring(0, 4)}; - req = mozContacts.find(defaultOptions); + req = mozContacts.find({}); req.onsuccess = function () { is(req.result.length, 2, "2 Entries."); next(); @@ -1224,14 +1213,11 @@ var steps = [ }, function () { ok(true, "Search with redundant fields should only return 1 contact"); - createResult1 = new mozContact({name: ["XXX"], - givenName: ["XXX"], - email: [{value: "XXX"}], - tel: [{value: "XXX"}] - }); + createResult1 = new mozContact(); + createResult1.init({name: "XXX", givenName: "XXX", email: [{value: "XXX"}], tel: {value: "XXX"}}); req = mozContacts.save(createResult1); req.onsuccess = function() { - var options = {filterBy: ["givenName", "familyName"], + var options = {filterBy: [], filterOp: "equals", filterValue: "XXX"}; var req2 = mozContacts.find(options); @@ -1254,7 +1240,8 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(c3); + createResult1 = new mozContact(); + createResult1.init(c3); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1265,7 +1252,8 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(c2); + createResult1 = new mozContact(); + createResult1.init(c2); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1276,7 +1264,8 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(c4); + createResult1 = new mozContact(); + createResult1.init(c4); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1287,7 +1276,8 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(c1); + createResult1 = new mozContact(); + createResult1.init(c1); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1328,7 +1318,8 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(c5); + createResult1 = new mozContact(); + createResult1.init(c5); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1355,16 +1346,17 @@ var steps = [ }, function () { ok(true, "Don't allow to add custom fields"); - createResult1 = new mozContact({givenName: ["customTest"], yyy: "XXX"}); + createResult1 = new mozContact(); + createResult1.init({givenName: "customTest", yyy: "XXX"}); req = mozContacts.save(createResult1); req.onsuccess = function() { - var options = {filterBy: ["givenName"], + var options = {filterBy: [], filterOp: "equals", filterValue: "customTest"}; var req2 = mozContacts.find(options); req2.onsuccess = function() { is(req2.result.length, 1, "1 Entry"); - checkStrArray(req2.result[0].givenName, ["customTest"], "same name"); + checkStrArray(req2.result.givenName, "customTest", "same name"); ok(req2.result.yyy === undefined, "custom property undefined"); next(); } @@ -1383,7 +1375,8 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(c7); + createResult1 = new mozContact(); + createResult1.init(c7); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1394,7 +1387,8 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(c6); + createResult1 = new mozContact(); + createResult1.init(c6); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1405,7 +1399,8 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(c8); + createResult1 = new mozContact(); + createResult1.init(c8); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1435,7 +1430,7 @@ var steps = [ }, function () { ok(true, "Deleting database"); - req = mozContacts.clear(); + req = mozContacts.clear() req.onsuccess = function () { ok(true, "Deleted the database"); next(); @@ -1444,7 +1439,8 @@ var steps = [ }, function () { ok(true, "Adding a new contact with properties2"); - createResult2 = new mozContact(properties2); + createResult2 = new mozContact(); + createResult2.init(properties2); req = mozContacts.save(createResult2); req.onsuccess = function () { ok(createResult2.id, "The contact now has an ID."); @@ -1489,8 +1485,9 @@ var steps = [ req.onerror = onFailure; }, function () { - ok(true, "Adding contact for category search"); - createResult1 = new mozContact({name: ["5"], givenName: ["5"]}); + ok(true, "Adding empty contact"); + createResult1 = new mozContact(); + createResult1.init({name: "5", givenName: "5"}); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1525,10 +1522,20 @@ var steps = [ ok(true, "Adding contact with invalid data"); var input = document.createElement("input"); var obj = { + name: [1, 2], + familyName: 3, + givenName: 4, honorificPrefix: [], - honorificSuffix: [{foo: "bar"}], + honorificSuffix: {foo: "bar"}, + additionalName: 7, + nickname: [8, 9], + org: [10, 11], + jobTitle: [12, 13], + note: 14, + category: [15, 16], sex: 17, genderIdentity: 18, + key: 4, email: input, adr: input, tel: input, @@ -1546,12 +1553,12 @@ var steps = [ } } })()); - createResult1 = new mozContact(obj); + createResult1 = new mozContact(); + createResult1.init(obj); req = mozContacts.save(createResult1); req.onsuccess = function () { checkContacts(createResult1, { - honorificPrefix: ["string"], - honorificSuffix: ["[object Object]"], + honorificPrefix: "string", sex: "17", genderIdentity: "18" }); @@ -1560,7 +1567,8 @@ var steps = [ }, function () { ok(true, "Adding contact with no number but carrier"); - createResult1 = new mozContact({ tel: [{type: ["home"], carrier: "myCarrier"} ] }); + createResult1 = new mozContact(); + createResult1.init({ tel: [{type: ["home"], carrier: "myCarrier"} ] }); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1570,7 +1578,8 @@ var steps = [ }, function () { ok(true, "Adding contact with email but no value"); - createResult1 = new mozContact({ email: [{type: ["home"]}] }); + createResult1 = new mozContact(); + createResult1.init({ email: [{type: ["home"]}] }); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1580,7 +1589,8 @@ var steps = [ }, function () { ok(true, "Testing numbersOnly search 1"); - createResult1 = new mozContact({ name: ["aaaaaaaaa"], givenName: ["aaaaaaaaa"], tel: [{ value: "1234567890"}]}); + createResult1 = new mozContact(); + createResult1.init({ name: ["aaaaaaaaa"], givenName: ["aaaaaaaaa"], tel: [{ value: "1234567890"}]}); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1660,10 +1670,17 @@ var steps = [ }, function() { ok(true, "Test setting array properties to scalar values") + const DOMStrings = ["name","honorificPrefix","givenName","additionalName", + "familyName", "honorificSuffix","nickname","category", + "org","jobTitle","note"]; const FIELDS = ["email","url","adr","tel","impp"]; createResult1 = new mozContact(); + for (var prop of DOMStrings) { + createResult1[prop] = "foo"; + ok(Array.isArray(createResult1[prop]), prop + " is array"); + } for (var prop of FIELDS) { - createResult1[prop] = {type: ["foo"]}; + createResult1[prop] = {type: "foo"}; ok(Array.isArray(createResult1[prop]), prop + " is array"); } next(); diff --git a/dom/contacts/tests/test_contacts_blobs.html b/dom/contacts/tests/test_contacts_blobs.html index 559ea927000e..494218edcc70 100644 --- a/dom/contacts/tests/test_contacts_blobs.html +++ b/dom/contacts/tests/test_contacts_blobs.html @@ -90,14 +90,14 @@ var randomBlob = getRandomBlob(1024); var randomBlob2 = getRandomBlob(1024); var properties1 = { - name: ["xTestname1"], - givenName: ["xTestname1"], + name: "xTestname1", + givenName: "xTestname1", photo: [randomBlob] }; var properties2 = { - name: ["yTestname2"], - givenName: ["yTestname2"], + name: "yTestname2", + givenName: "yTestname2", photo: [randomBlob, randomBlob2] }; @@ -177,7 +177,8 @@ var steps = [ }, function () { ok(true, "Adding contact with photo"); - createResult1 = new mozContact(properties1); + createResult1 = new mozContact(); + createResult1.init(properties1); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -190,7 +191,7 @@ var steps = [ ok(true, "Retrieving by substring"); var options = {filterBy: ["givenName"], filterOp: "startsWith", - filterValue: properties1.givenName[0].substring(0,3)}; + filterValue: properties1.givenName.substring(0,3)}; req = mozContacts.find(options); req.onsuccess = function () { ok(req.result.length == 1, "Found exactly 1 contact."); @@ -202,7 +203,8 @@ var steps = [ }, function () { ok(true, "Adding contact with 2 photos"); - createResult1 = new mozContact(properties2); + createResult1 = new mozContact(); + createResult1.init(properties2); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -215,7 +217,7 @@ var steps = [ ok(true, "Retrieving by substring"); var options = {filterBy: ["givenName"], filterOp: "startsWith", - filterValue: properties2.givenName[0].substring(0,3)}; + filterValue: properties2.givenName.substring(0,3)}; req = mozContacts.find(options); req.onsuccess = function () { ok(req.result.length == 1, "Found exactly 1 contact."); @@ -225,6 +227,58 @@ var steps = [ }; req.onerror = onFailure; }, + function () { + ok(true, "Adding photo as String"); + createResult1 = new mozContact(); + createResult1.init({givenName: "asdf", photo: ["xyz"]}); + req = navigator.mozContacts.save(createResult1); + req.onsuccess = function () { + ok(createResult1.id, "The contact now has an ID."); + sample_id1 = createResult1.id; + is(createResult1.photo, null, "No photo") + next(); + }; + req.onerror = onFailure; + }, + function () { + ok(true, "Adding photo as String"); + createResult1 = new mozContact(); + createResult1.init({givenName: "jkl", photo: "xyz"}); + req = navigator.mozContacts.save(createResult1); + req.onsuccess = function () { + ok(createResult1.id, "The contact now has an ID."); + is(createResult1.photo, null, "No photo") + next(); + }; + req.onerror = onFailure; + }, + function () { + ok(true, "Retrieving by substring"); + var options = {filterBy: ["givenName"], + filterOp: "startsWith", + filterValue: "asdf"}; + 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"); + is(findResult1.photo, null, "No photo"); + next(); + }; + req.onerror = onFailure; + }, + function () { + ok(true, "Adding photo as Object"); + createResult1 = new mozContact(); + createResult1.init({photo: [{}]}); + req = navigator.mozContacts.save(createResult1); + req.onsuccess = function () { + ok(createResult1.id, "The contact now has an ID."); + is(createResult1.photo, null, "No photo") + next(); + }; + req.onerror = onFailure; + }, function () { ok(true, "Deleting database"); req = mozContacts.clear() diff --git a/dom/contacts/tests/test_contacts_getall.html b/dom/contacts/tests/test_contacts_getall.html index daccff0f25a2..acc0d62a4778 100644 --- a/dom/contacts/tests/test_contacts_getall.html +++ b/dom/contacts/tests/test_contacts_getall.html @@ -36,7 +36,7 @@ var androidVersion = SpecialPowers.Cc['@mozilla.org/system-info;1'] .getProperty('version'); let adr1 = { - type: ["work"], + type: "work", streetAddress: "street 1", locality: "locality 1", region: "region 1", @@ -48,9 +48,9 @@ let properties1 = { name: ["Testname1 TestFamilyName"], familyName: ["TestFamilyName","Wagner"], givenName: ["Test1","Test2"], - nickname: ["nicktest"], + nickname: "nicktest", tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+9-876-5432"}], - adr: [adr1], + adr: adr1, email: [{type: ["work"], value: "x@y.com"}] }; @@ -58,137 +58,102 @@ function onFailure() { ok(false, "in on Failure!"); next(); } + function checkStr(str1, str2, msg) { - if (str1 ^ str2) { - ok(false, "Expected both strings to be either present or absent"); - return; + // comparing /[null(,null)+]/ and undefined should pass + function nonNull(e) { + return e != null; } - is(str1, str2, msg); -} - -function checkStrArray(str1, str2, msg) { - function normalize_falsy(k, v) { - if (!v || v == "null" || v == "undefined") { - return ""; - } - return v; + if ((Array.isArray(str1) && str1.filter(nonNull).length == 0 && str2 == undefined) + ||(Array.isArray(str2) && str2.filter(nonNull).length == 0 && str1 == undefined)) { + ok(true, msg); + } else if (str1) { + is(JSON.stringify(typeof str1 == "string" ? [str1] : str1), JSON.stringify(typeof str2 == "string" ? [str2] : str2), msg); } - ise(JSON.stringify(str1, normalize_falsy), JSON.stringify(str2, normalize_falsy), msg); -} - -function checkPref(pref1, pref2) { - // If on Android treat one preference as 0 and the other as undefined as matching - if (isAndroid) { - if ((!pref1 && pref2 == undefined) || (pref1 == undefined && !pref2)) { - pref1 = false; - pref2 = false; - } - } - ise(!!pref1, !!pref2, "Same pref"); } function checkAddress(adr1, adr2) { - if (adr1 ^ adr2) { - ok(false, "Expected both adrs to be either present or absent"); - return; - } - checkStrArray(adr1.type, adr2.type, "Same type"); - checkStrArray(adr1.streetAddress, adr2.streetAddress, "Same streetAddress"); - checkStrArray(adr1.locality, adr2.locality, "Same locality"); - checkStrArray(adr1.region, adr2.region, "Same region"); - checkStrArray(adr1.postalCode, adr2.postalCode, "Same postalCode"); - checkStrArray(adr1.countryName, adr2.countryName, "Same countryName"); - checkPref(adr1.pref, adr2.pref); -} - -function checkField(field1, field2) { - if (field1 ^ field2) { - ok(false, "Expected both fields to be either present or absent"); - return; - } - checkStrArray(field1.type, field2.type, "Same type"); - checkStrArray(field1.value, field2.value, "Same value"); - checkPref(field1.pref, field2.pref); + checkStr(adr1.type, adr2.type, "Same type"); + checkStr(adr1.streetAddress, adr2.streetAddress, "Same streetAddress"); + checkStr(adr1.locality, adr2.locality, "Same locality"); + checkStr(adr1.region, adr2.region, "Same region"); + checkStr(adr1.postalCode, adr2.postalCode, "Same postalCode"); + checkStr(adr1.countryName, adr2.countryName, "Same countryName"); } function checkTel(tel1, tel2) { - if (tel1 ^ tel2) { - ok(false, "Expected both tels to be either present or absent"); - return; - } - checkField(tel1, tel2); - checkStrArray(tel1.carrier, tel2.carrier, "Same carrier"); + checkStr(tel1.type, tel2.type, "Same type"); + checkStr(tel1.value, tel2.value, "Same value"); + checkStr(tel1.carrier, tel2.carrier, "Same carrier"); } -function checkCategory(category1, category2) { - // Android adds contacts to the a default category. This should be removed from the - // results before comparing them - if (isAndroid) { - category1 = removeAndroidDefaultCategory(category1); - category2 = removeAndroidDefaultCategory(category2); - } - checkStrArray(category1, category2, "Same Category") -} - -function removeAndroidDefaultCategory(category) { - if (!category) { - return category; - } - - var result = []; - - for (var i of category) { - // Some devices may return the full group name (prefixed with "System Group: ") - if (i != "My Contacts" && i != "System Group: My Contacts") { - result.push(i); - } - } - - return result; -} - -function checkArrayField(array1, array2, func, msg) { - if (!!array1 ^ !!array2) { - ok(false, "Expected both arrays to be either present or absent"); - return; - } - if (!array1 && !array2) { - ok(true, msg); - return; - } - ise(array1.length, array2.length, "Same length"); - for (var i = 0; i < array1.length; ++i) { - func(array1[i], array2[i], msg); - } +function checkField(field1, field2) { + checkStr(field1.type, field2.type, "Same type"); + checkStr(field1.value, field2.value, "Same value"); } function checkContacts(contact1, contact2) { - if (!!contact1 ^ !!contact2) { - ok(false, "Expected both contacts to be either present or absent"); - return; - } - checkStrArray(contact1.name, contact2.name, "Same name"); - checkStrArray(contact1.honorificPrefix, contact2.honorificPrefix, "Same honorificPrefix"); - checkStrArray(contact1.givenName, contact2.givenName, "Same givenName"); - checkStrArray(contact1.additionalName, contact2.additionalName, "Same additionalName"); - checkStrArray(contact1.familyName, contact2.familyName, "Same familyName"); - checkStrArray(contact1.honorificSuffix, contact2.honorificSuffix, "Same honorificSuffix"); - checkStrArray(contact1.nickname, contact2.nickname, "Same nickname"); - checkCategory(contact1.category, contact2.category); - checkStrArray(contact1.org, contact2.org, "Same org"); - checkStrArray(contact1.jobTitle, contact2.jobTitle, "Same jobTitle"); + checkStr(contact1.name, contact2.name, "Same name"); + checkStr(contact1.honorificPrefix, contact2.honorificPrefix, "Same honorificPrefix"); + checkStr(contact1.givenName, contact2.givenName, "Same givenName"); + checkStr(contact1.additionalName, contact2.additionalName, "Same additionalName"); + checkStr(contact1.familyName, contact2.familyName, "Same familyName"); + checkStr(contact1.honorificSuffix, contact2.honorificSuffix, "Same honorificSuffix"); + checkStr(contact1.nickname, contact2.nickname, "Same nickname"); + checkStr(contact1.category, contact2.category, "Same category"); + checkStr(contact1.org, contact2.org, "Same org"); + checkStr(contact1.jobTitle, contact2.jobTitle, "Same jobTitle"); is(contact1.bday ? contact1.bday.valueOf() : null, contact2.bday ? contact2.bday.valueOf() : null, "Same birthday"); - checkStrArray(contact1.note, contact2.note, "Same note"); + checkStr(contact1.note, contact2.note, "Same note"); is(contact1.anniversary ? contact1.anniversary.valueOf() : null , contact2.anniversary ? contact2.anniversary.valueOf() : null, "Same anniversary"); - checkStr(contact1.sex, contact2.sex, "Same sex"); - checkStr(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity"); - checkStrArray(contact1.key, contact2.key, "Same key"); + is(contact1.sex, contact2.sex, "Same sex"); + is(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity"); - checkArrayField(contact1.adr, contact2.adr, checkAddress, "Same adr"); - checkArrayField(contact1.tel, contact2.tel, checkTel, "Same tel"); - checkArrayField(contact1.email, contact2.email, checkField, "Same email"); - checkArrayField(contact1.url, contact2.url, checkField, "Same url"); - checkArrayField(contact1.impp, contact2.impp, checkField, "Same impp"); + for (let i in contact1.email) { + if (contact1.email) { + ok(contact2.email != null, "conatct2.email exists"); + } + if (contact2.email) { + ok(contact1.email != null, "conatct1.email exists"); + } + checkField(contact1.email[i], contact2.email[i]); + } + for (let i in contact1.adr) { + if (contact1.adr) { + ok(contact2.adr != null, "conatct2.adr exists"); + } + if (contact2.adr) { + ok(contact1.adr != null, "conatct1.adr exists"); + } + checkAddress(contact1.adr[i], contact2.adr[i]); + } + for (let i in contact1.tel) { + if (contact1.tel) { + ok(contact2.tel != null, "conatct2.tel exists"); + } + if (contact2.tel) { + ok(contact1.tel != null, "conatct1.tel exists"); + } + checkTel(contact1.tel[i], contact2.tel[i]); + } + for (let i in contact1.url) { + if (contact1.url) { + ok(contact2.url != null, "conatct2.url exists"); + } + if (contact2.url) { + ok(contact1.url != null, "conatct1.url exists"); + } + checkField(contact1.url[i], contact2.url[i]); + } + for (let i in contact1.impp) { + if (contact1.impp) { + ok(contact2.impp != null, "conatct2.impp exists"); + } + if (contact2.impp) { + ok(contact1.impp != null, "conatct1.impp exists"); + } + checkField(contact1.impp[i], contact2.impp[i]); + } } function clearDatabase() { @@ -204,22 +169,24 @@ function clearDatabase() { function addContacts() { ok(true, "Adding 40 contacts"); for (let i = 0; i < 39; ++i) { + createResult1 = new mozContact(); properties1.familyName[0] = "Testname" + (i < 10 ? "0" + i : i); - properties1.name = [properties1.givenName[0] + " " + properties1.familyName[0]]; - createResult1 = new mozContact(properties1); + properties1.name = properties1.givenName[0] + " " + properties1.familyName[0]; + createResult1.init(properties1); req = mozContacts.save(createResult1); req.onsuccess = function() { ok(createResult1.id, "The contact now has an ID."); }; req.onerror = onFailure; }; + createResult1 = new mozContact(); properties1.familyName[0] = "Testname39"; - properties1.name = [properties1.givenName[0] + " Testname39"]; - createResult1 = new mozContact(properties1); + properties1.name = properties1.givenName[0] + " Testname39"; + createResult1.init(properties1); req = mozContacts.save(createResult1); req.onsuccess = function() { ok(createResult1.id, "The contact now has an ID."); - checkStrArray(createResult1.name, properties1.name, "Same Name"); + ok(createResult1.name == properties1.name, "Same Name"); next(); }; req.onerror = onFailure; @@ -235,7 +202,6 @@ function getOne(msg) { return function() { ok(true, msg || "Retrieving one contact with getAll"); req = mozContacts.getAll({}); - let count = 0; req.onsuccess = function(event) { ok(true, "on success"); @@ -280,14 +246,11 @@ function getAll(msg) { } let steps = [ - function start() { - SpecialPowers.Cc["@mozilla.org/tools/profiler;1"].getService(SpecialPowers.Ci.nsIProfiler).AddMarker("GETALL_START"); - next(); - }, clearDatabase, function() { // add a contact - createResult1 = new mozContact({}); + createResult1 = new mozContact(); + createResult1.init({}); req = navigator.mozContacts.save(createResult1); req.onsuccess = function() { next(); @@ -478,7 +441,6 @@ let steps = [ function() { ok(true, "all done!\n"); - SpecialPowers.Cc["@mozilla.org/tools/profiler;1"].getService(SpecialPowers.Ci.nsIProfiler).AddMarker("GETALL_END"); SimpleTest.finish(); } ]; diff --git a/dom/contacts/tests/test_contacts_international.html b/dom/contacts/tests/test_contacts_international.html index 4b14bd03248b..358aa52126d5 100644 --- a/dom/contacts/tests/test_contacts_international.html +++ b/dom/contacts/tests/test_contacts_international.html @@ -51,13 +51,13 @@ var number2 = { }; var properties1 = { - name: ["Testname1"], + name: "Testname1", tel: [{type: ["work"], value: number1.local, carrier: "testCarrier"} , {type: ["home", "fax"], value: number2.local}], }; var shortNumber = "888"; var properties2 = { - name: ["Testname2"], + name: "Testname2", tel: [{type: ["work"], value: shortNumber, carrier: "testCarrier"}] }; @@ -81,7 +81,8 @@ var steps = [ }, function () { ok(true, "Adding a new contact1"); - createResult1 = new mozContact(properties1); + createResult1 = new mozContact(); + createResult1.init(properties1); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -92,7 +93,8 @@ var steps = [ }, function () { ok(true, "Adding a new contact2"); - var createResult2 = new mozContact(properties2); + var createResult2 = new mozContact(); + createResult2.init(properties2); req = navigator.mozContacts.save(createResult2); req.onsuccess = function () { ok(createResult2.id, "The contact now has an ID."); diff --git a/dom/contacts/tests/test_contacts_substringmatching.html b/dom/contacts/tests/test_contacts_substringmatching.html index c71d1a2639d6..b5fea828a9ca 100644 --- a/dom/contacts/tests/test_contacts_substringmatching.html +++ b/dom/contacts/tests/test_contacts_substringmatching.html @@ -82,7 +82,8 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(prop); + createResult1 = new mozContact(); + createResult1.init(prop); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -165,7 +166,8 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(prop2); + createResult1 = new mozContact(); + createResult1.init(prop2); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -257,7 +259,8 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(prop3); + createResult1 = new mozContact(); + createResult1.init(prop3); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -286,7 +289,8 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(prop4); + createResult1 = new mozContact(); + createResult1.init(prop4); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); diff --git a/dom/contacts/tests/test_contacts_substringmatchingVE.html b/dom/contacts/tests/test_contacts_substringmatchingVE.html index 484abb9afbbe..0b226a490dac 100644 --- a/dom/contacts/tests/test_contacts_substringmatchingVE.html +++ b/dom/contacts/tests/test_contacts_substringmatchingVE.html @@ -73,7 +73,8 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(prop); + createResult1 = new mozContact(); + createResult1.init(prop); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -111,7 +112,8 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(prop2); + createResult1 = new mozContact(); + createResult1.init(prop2); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); diff --git a/dom/icc/interfaces/nsIDOMIccManager.idl b/dom/icc/interfaces/nsIDOMIccManager.idl index d5ca00829e10..0102e788f247 100644 --- a/dom/icc/interfaces/nsIDOMIccManager.idl +++ b/dom/icc/interfaces/nsIDOMIccManager.idl @@ -5,6 +5,7 @@ #include "nsIDOMEventTarget.idl" #include "SimToolKit.idl" +interface nsIDOMContact; interface nsIDOMDOMRequest; interface nsIDOMEventListener; interface nsIDOMMozIccInfo; @@ -506,7 +507,7 @@ interface nsIDOMMozIccManager : nsIDOMEventTarget * PIN2 is only required for 'fdn'. */ nsIDOMDOMRequest updateContact(in DOMString contactType, - in nsISupports contact, + in nsIDOMContact contact, [optional] in DOMString pin2); // End of UICC Phonebook Interfaces. diff --git a/dom/icc/interfaces/nsIIccProvider.idl b/dom/icc/interfaces/nsIIccProvider.idl index 0620a98e22ab..ac98ca81220b 100644 --- a/dom/icc/interfaces/nsIIccProvider.idl +++ b/dom/icc/interfaces/nsIIccProvider.idl @@ -4,6 +4,7 @@ #include "nsISupports.idl" +interface nsIDOMContact; interface nsIDOMDOMRequest; interface nsIDOMMozIccInfo; interface nsIDOMWindow; @@ -73,7 +74,7 @@ interface nsIIccProvider : nsISupports nsIDOMDOMRequest updateContact(in nsIDOMWindow window, in DOMString contactType, - in nsISupports contact, + in nsIDOMContact contact, in DOMString pin2); /** diff --git a/dom/icc/src/IccManager.cpp b/dom/icc/src/IccManager.cpp index 7e302dda3a99..92709dfe8cd8 100644 --- a/dom/icc/src/IccManager.cpp +++ b/dom/icc/src/IccManager.cpp @@ -235,7 +235,7 @@ IccManager::ReadContacts(const nsAString& aContactType, nsIDOMDOMRequest** aRequ NS_IMETHODIMP IccManager::UpdateContact(const nsAString& aContactType, - nsISupports* aContact, + nsIDOMContact* aContact, const nsAString& aPin2, nsIDOMDOMRequest** aRequest) { diff --git a/dom/icc/tests/marionette/test_icc_contact.js b/dom/icc/tests/marionette/test_icc_contact.js index b7c75c7c83c0..ef5ac8c928c4 100644 --- a/dom/icc/tests/marionette/test_icc_contact.js +++ b/dom/icc/tests/marionette/test_icc_contact.js @@ -15,19 +15,19 @@ function testReadContacts(type) { is(Array.isArray(contacts), true); - is(contacts[0].name[0], "Mozilla"); + is(contacts[0].name, "Mozilla"); is(contacts[0].tel[0].value, "15555218201"); is(contacts[0].id, "890141032111185107201"); - is(contacts[1].name[0], "Saßê黃"); + is(contacts[1].name, "Saßê黃"); is(contacts[1].tel[0].value, "15555218202"); is(contacts[1].id, "890141032111185107202"); - is(contacts[2].name[0], "Fire 火"); + is(contacts[2].name, "Fire 火"); is(contacts[2].tel[0].value, "15555218203"); is(contacts[2].id, "890141032111185107203"); - is(contacts[3].name[0], "Huang 黃"); + is(contacts[3].name, "Huang 黃"); is(contacts[3].tel[0].value, "15555218204"); is(contacts[3].id, "890141032111185107204"); @@ -41,8 +41,10 @@ function testReadContacts(type) { }; function testAddContact(type, pin2) { - let contact = new mozContact({ - name: ["add"], + let contact = new mozContact(); + + contact.init({ + name: "add", tel: [{value: "0912345678"}], email:[] }); @@ -60,7 +62,7 @@ function testAddContact(type, pin2) { // There are 4 SIM contacts which are harded in emulator is(contacts.length, 5); - is(contacts[4].name[0], "add"); + is(contacts[4].name, "add"); is(contacts[4].tel[0].value, "0912345678"); runNextTest(); diff --git a/dom/interfaces/contacts/moz.build b/dom/interfaces/contacts/moz.build new file mode 100644 index 000000000000..d7fc401f3fe6 --- /dev/null +++ b/dom/interfaces/contacts/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + 'nsIContactProperties.idl', + 'nsIDOMContactManager.idl', + 'nsIDOMMozContactChangeEvent.idl', +] + +XPIDL_MODULE = 'dom_contacts' + +MODULE = 'dom' + diff --git a/dom/interfaces/contacts/nsIContactProperties.idl b/dom/interfaces/contacts/nsIContactProperties.idl new file mode 100644 index 000000000000..e5fe19f41537 --- /dev/null +++ b/dom/interfaces/contacts/nsIContactProperties.idl @@ -0,0 +1,74 @@ +/* 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" + +[scriptable, uuid(9cbfa81c-bcab-4ca9-b0d2-f4318f295e33)] +interface nsIContactAddress : nsISupports +{ + attribute DOMString type; + attribute boolean pref; // false = no pref, true = preferred (vCard3 TYPE:PREF; vCard4 PREF:1) + attribute DOMString streetAddress; + attribute DOMString locality; + attribute DOMString region; + attribute DOMString postalCode; + attribute DOMString countryName; +}; + +[scriptable, uuid(ad19a543-69e4-44f0-adfa-37c011556bc1)] +interface nsIContactField : nsISupports +{ + attribute jsval type; // DOMString[], "home", "work", etc. + attribute DOMString value; + attribute boolean pref; // false = no pref, true = preferred (vCard3 TYPE:PREF; vCard4 PREF:1) +}; + +[scriptable, uuid(4d42c5a9-ea5d-4102-80c3-40cc986367ca)] +interface nsIContactTelField : nsIContactField +{ + attribute DOMString carrier; +}; + +[scriptable, uuid(0a5b1fab-70da-46dd-b902-619904d920c2)] +interface nsIContactFindSortOptions : nsISupports +{ + attribute DOMString sortBy; // "givenName" or "familyName" + attribute DOMString sortOrder; // e.g. "descending" +}; + +[scriptable, uuid(28ce07d0-45d9-4b7a-8843-521df4edd8bc)] +interface nsIContactFindOptions : nsIContactFindSortOptions +{ + attribute DOMString filterValue; // e.g. "Tom" + attribute DOMString filterOp; // e.g. "startsWith" + attribute jsval filterBy; // DOMString[], e.g. ["givenName", "nickname"] + attribute unsigned long filterLimit; +}; + +[scriptable, uuid(35ad8a4e-9486-44b6-883d-550f14635e49)] +interface nsIContactProperties : nsISupports +{ + attribute jsval name; // DOMString[] + attribute jsval honorificPrefix; // DOMString[] + attribute jsval givenName; // DOMString[] + attribute jsval additionalName; // DOMString[] + attribute jsval familyName; // DOMString[] + attribute jsval honorificSuffix; // DOMString[] + attribute jsval nickname; // DOMString[] + attribute jsval email; // ContactField[] + attribute jsval photo; // nsIDOMBlob[] + attribute jsval url; // ContactField[] + attribute jsval category; // DOMString[] + attribute jsval adr; // ContactAddress[] + attribute jsval tel; // ContactTelField[] + attribute jsval org; // DOMString[] + attribute jsval jobTitle; // DOMString[] + attribute jsval bday; // Date + attribute jsval note; // DOMString[] + attribute jsval impp; // ContactField[] + attribute jsval anniversary; // Date + attribute DOMString sex; // DOMString + attribute DOMString genderIdentity; // DOMString + attribute jsval key; // DOMString[] +}; diff --git a/dom/interfaces/contacts/nsIDOMContactManager.idl b/dom/interfaces/contacts/nsIDOMContactManager.idl new file mode 100644 index 000000000000..5ae8a3d31620 --- /dev/null +++ b/dom/interfaces/contacts/nsIDOMContactManager.idl @@ -0,0 +1,41 @@ +/* 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 "nsIContactProperties.idl" +#include "nsIDOMEventTarget.idl" + +interface nsIArray; +interface nsIDOMDOMRequest; +interface nsIDOMDOMCursor; + +[scriptable, uuid(72a5ee28-81d8-4af8-90b3-ae935396cc66)] +interface nsIDOMContact : nsIContactProperties +{ + attribute DOMString id; + readonly attribute jsval published; + readonly attribute jsval updated; + + void init(in nsIContactProperties properties); // Workaround BUG 723206 +}; + +[scriptable, uuid(8beb3a66-d70a-4111-b216-b8e995ad3aff)] +interface nsIDOMContactManager : nsISupports +{ + nsIDOMDOMRequest find(in nsIContactFindOptions options); + + nsIDOMDOMCursor getAll(in nsIContactFindSortOptions options); + + nsIDOMDOMRequest clear(); + + nsIDOMDOMRequest save(in nsIDOMContact contact); + + nsIDOMDOMRequest remove(in nsIDOMContact contact); + + attribute nsIDOMEventListener oncontactchange; + + nsIDOMDOMRequest getRevision(); + + nsIDOMDOMRequest getCount(); +}; diff --git a/dom/interfaces/contacts/nsIDOMMozContactChangeEvent.idl b/dom/interfaces/contacts/nsIDOMMozContactChangeEvent.idl new file mode 100644 index 000000000000..335983000f34 --- /dev/null +++ b/dom/interfaces/contacts/nsIDOMMozContactChangeEvent.idl @@ -0,0 +1,24 @@ +/* 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 "nsIDOMEvent.idl" + +[scriptable, builtinclass, uuid(7ee758eb-9353-4ade-8715-9953ea512ee2)] +interface nsIDOMMozContactChangeEvent : nsIDOMEvent +{ + readonly attribute DOMString contactID; + readonly attribute DOMString reason; + + [noscript] void initMozContactChangeEvent(in DOMString aType, + in boolean aCanBubble, + in boolean aCancelable, + in DOMString aContactID, + in DOMString aReason); +}; + +dictionary MozContactChangeEventInit : EventInit +{ + DOMString contactID; + DOMString reason; +}; diff --git a/dom/moz.build b/dom/moz.build index 4ec960bd184b..fa81912aaa4c 100644 --- a/dom/moz.build +++ b/dom/moz.build @@ -11,6 +11,7 @@ interfaces = [ 'html', 'events', 'devicestorage', + 'contacts', 'settings', 'stylesheets', 'sidebar', diff --git a/dom/system/gonk/RILContentHelper.js b/dom/system/gonk/RILContentHelper.js index 07ebd7210f19..bb7a390d1a9d 100644 --- a/dom/system/gonk/RILContentHelper.js +++ b/dom/system/gonk/RILContentHelper.js @@ -1671,6 +1671,7 @@ RILContentHelper.prototype = { delete this._windowsMap[message.requestId]; let contacts = message.contacts; let result = contacts.map(function(c) { + let contact = Cc["@mozilla.org/contact;1"].createInstance(Ci.nsIDOMContact); let prop = {name: [c.alphaId], tel: [{value: c.number}]}; if (c.email) { @@ -1683,7 +1684,7 @@ RILContentHelper.prototype = { prop.tel.push({value: c.anr[i]}); } - let contact = new window.mozContact(prop); + contact.init(prop); contact.id = message.iccid + c.recordId; return contact; }); diff --git a/dom/webidl/Contacts.webidl b/dom/webidl/Contacts.webidl deleted file mode 100644 index 6f594ba29212..000000000000 --- a/dom/webidl/Contacts.webidl +++ /dev/null @@ -1,170 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. - */ - -[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactAddress;1"] -interface ContactAddress { - attribute object? type; // DOMString[] - attribute DOMString? streetAddress; - attribute DOMString? locality; - attribute DOMString? region; - attribute DOMString? postalCode; - attribute DOMString? countryName; - attribute boolean? pref; - - [ChromeOnly] - void initialize(optional sequence? type, - optional DOMString streetAddress, - optional DOMString locality, - optional DOMString region, - optional DOMString postalCode, - optional DOMString countryName, - optional boolean pref); -}; - -dictionary ContactAddressInit { - sequence? type; - DOMString? streetAddress; - DOMString? locality; - DOMString? region; - DOMString? postalCode; - DOMString? countryName; - boolean? pref; -}; - - -[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactField;1"] -interface ContactField { - attribute object? type; // DOMString[] - attribute DOMString? value; - attribute boolean? pref; - - [ChromeOnly] - void initialize(optional sequence? type, - optional DOMString value, - optional boolean pref); -}; - -dictionary ContactFieldInit { - sequence? type; - DOMString? value; - boolean? pref; -}; - - -[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactTelField;1"] -interface ContactTelField : ContactField { - attribute DOMString? carrier; - - [ChromeOnly] - void initialize(optional sequence? type, - optional DOMString value, - optional DOMString? carrier, - optional boolean pref); -}; - -dictionary ContactTelFieldInit : ContactFieldInit { - DOMString? carrier; -}; - - -dictionary ContactProperties { - Date? bday; - Date? anniversary; - - DOMString? sex; - DOMString? genderIdentity; - - sequence? photo; - - sequence? adr; - - sequence? email; - sequence? url; - sequence? impp; - - sequence? tel; - - sequence? name; - sequence? honorificPrefix; - sequence? givenName; - sequence? additionalName; - sequence? familyName; - sequence? honorificSuffix; - sequence? nickname; - sequence? category; - sequence? org; - sequence? jobTitle; - sequence? note; - sequence? key; -}; - -[Constructor(optional ContactProperties properties), - JSImplementation="@mozilla.org/contact;1"] -interface mozContact { - attribute DOMString id; - readonly attribute Date? published; - readonly attribute Date? updated; - - attribute Date? bday; - attribute Date? anniversary; - - attribute DOMString? sex; - attribute DOMString? genderIdentity; - - attribute object? photo; - - attribute object? adr; - - attribute object? email; - attribute object? url; - attribute object? impp; - - attribute object? tel; - - attribute object? name; - attribute object? honorificPrefix; - attribute object? givenName; - attribute object? additionalName; - attribute object? familyName; - attribute object? honorificSuffix; - attribute object? nickname; - attribute object? category; - attribute object? org; - attribute object? jobTitle; - attribute object? note; - attribute object? key; - - [ChromeOnly] - void setMetadata(DOMString id, Date? published, Date? updated); - - jsonifier; -}; - -dictionary ContactFindSortOptions { - DOMString sortBy; // "givenName" or "familyName" - DOMString sortOrder = "ascending"; // e.g. "descending" -}; - -dictionary ContactFindOptions : ContactFindSortOptions { - DOMString filterValue; // e.g. "Tom" - DOMString filterOp; // e.g. "startsWith" - any filterBy; // e.g. ["givenName", "nickname"] - unsigned long filterLimit = 0; -}; - -[NoInterfaceObject, NavigatorProperty="mozContacts", - JSImplementation="@mozilla.org/contactManager;1"] -interface ContactManager : EventTarget { - DOMRequest find(optional ContactFindOptions options); - DOMCursor getAll(optional ContactFindSortOptions options); - DOMRequest clear(); - DOMRequest save(mozContact contact); - DOMRequest remove(mozContact contact); - DOMRequest getRevision(); - DOMRequest getCount(); - - attribute EventHandler oncontactchange; -}; diff --git a/dom/webidl/MozContactChangeEvent.webidl b/dom/webidl/MozContactChangeEvent.webidl index 95b695b2a78a..d31087276727 100644 --- a/dom/webidl/MozContactChangeEvent.webidl +++ b/dom/webidl/MozContactChangeEvent.webidl @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ -[Constructor(DOMString type, optional MozContactChangeEventInit eventInitDict)] +[Constructor(DOMString type, optional MozContactChangeEventInit eventInitDict), HeaderFile="GeneratedEventClasses.h"] interface MozContactChangeEvent : Event { readonly attribute DOMString? contactID; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 155dbc6ebbf9..6fb447e30f27 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -53,7 +53,6 @@ WEBIDL_FILES = [ 'CommandEvent.webidl', 'Comment.webidl', 'CompositionEvent.webidl', - 'Contacts.webidl', 'ConvolverNode.webidl', 'Coordinates.webidl', 'CSS.webidl', @@ -471,6 +470,7 @@ WEBIDL_FILES += [ 'ElementReplaceEvent.webidl', 'HashChangeEvent.webidl', 'MozApplicationEvent.webidl', + 'MozContactChangeEvent.webidl', 'MozMmsEvent.webidl', 'MozSettingsEvent.webidl', 'MozSmsEvent.webidl', @@ -538,7 +538,6 @@ GENERATED_EVENTS_WEBIDL_FILES = [ 'DeviceLightEvent.webidl', 'DeviceProximityEvent.webidl', 'MediaStreamEvent.webidl', - 'MozContactChangeEvent.webidl', 'MozInterAppMessageEvent.webidl', 'RTCDataChannelEvent.webidl', 'RTCPeerConnectionIceEvent.webidl', diff --git a/js/xpconnect/src/event_impl_gen.conf.in b/js/xpconnect/src/event_impl_gen.conf.in index f952b67a2246..6d2361f43c62 100644 --- a/js/xpconnect/src/event_impl_gen.conf.in +++ b/js/xpconnect/src/event_impl_gen.conf.in @@ -17,6 +17,7 @@ simple_events = [ 'PopStateEvent', 'HashChangeEvent', 'CloseEvent', + 'MozContactChangeEvent', 'DeviceOrientationEvent', 'MozApplicationEvent', 'SmartCardEvent', diff --git a/mobile/android/base/ContactService.java b/mobile/android/base/ContactService.java index 80b4848f3747..36991bb3fddf 100644 --- a/mobile/android/base/ContactService.java +++ b/mobile/android/base/ContactService.java @@ -214,10 +214,10 @@ public class ContactService implements GeckoEventListener { sortBy = findOptions.optString("sortBy").toLowerCase(); sortOrder = findOptions.optString("sortOrder").toLowerCase(); - if ("".equals(sortBy)) { + if ("undefined".equals(sortBy)) { sortBy = null; } - if ("".equals(sortOrder)) { + if ("undefined".equals(sortOrder)) { sortOrder = "ascending"; } @@ -240,15 +240,15 @@ public class ContactService implements GeckoEventListener { try { final JSONObject findOptions = contactOptions.getJSONObject("findOptions"); - String filterValue = findOptions.optString("filterValue"); + String filterValue = findOptions.getString("filterValue"); JSONArray filterBy = findOptions.optJSONArray("filterBy"); - final String filterOp = findOptions.optString("filterOp"); + final String filterOp = findOptions.getString("filterOp"); final int filterLimit = findOptions.getInt("filterLimit"); final int substringMatching = findOptions.getInt("substringMatching"); // If filter value is undefined, avoid all the logic below and just return // all available raw contact IDs - if ("".equals(filterValue) || "".equals(filterOp)) { + if ("undefined".equals(filterValue)) { long[] allRawContactIds = getAllRawContactIds(); // Truncate the raw contacts IDs array if necessary @@ -513,7 +513,7 @@ public class ContactService implements GeckoEventListener { String anniversary = null; String sex = null; String genderIdentity = null; - JSONArray key = new JSONArray(); + String key = null; // Get all the data columns final String[] columnsToGet = getAllColumns(); @@ -614,7 +614,7 @@ public class ContactService implements GeckoEventListener { genderIdentity = cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN)); } else if (MIMETYPE_KEY.equals(mimeType)) { - key.put(cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN))); + key = cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN)); } } catch (JSONException e) { throw new IllegalArgumentException(e); @@ -640,17 +640,17 @@ public class ContactService implements GeckoEventListener { contactProperties.put("url", urls); contactProperties.put("impp", impps); contactProperties.put("category", categories); - contactProperties.put("key", key); putPossibleNullValueInJSONObject("bday", bday, contactProperties); putPossibleNullValueInJSONObject("anniversary", anniversary, contactProperties); putPossibleNullValueInJSONObject("sex", sex, contactProperties); putPossibleNullValueInJSONObject("genderIdentity", genderIdentity, contactProperties); + putPossibleNullValueInJSONObject("key", key, contactProperties); // Add the raw contact ID and the properties to the contact contact.put("id", String.valueOf(rawContactId)); - contact.put("updated", null); - contact.put("published", null); + contact.put("updated", "0000T00:00:00.000Z"); + contact.put("published", "0000T00:00:00.000Z"); contact.put("properties", contactProperties); } catch (JSONException e) { throw new IllegalArgumentException(e); @@ -665,10 +665,6 @@ public class ContactService implements GeckoEventListener { return contact; } - private boolean bool(int integer) { - return integer != 0 ? true : false; - } - private void getGenericDataAsJSONObject(Cursor cursor, JSONArray array, final String dataColumn, final String typeColumn, final String typeLabelColumn, final HashMap typeMap) throws JSONException { @@ -706,7 +702,7 @@ public class ContactService implements GeckoEventListener { object.put("value", value); types.put(type); object.put("type", types); - object.put("pref", bool(cursor.getInt(cursor.getColumnIndex(Data.IS_SUPER_PRIMARY)))); + object.put("pref", cursor.getInt(cursor.getColumnIndex(Data.IS_SUPER_PRIMARY))); array.put(object); } @@ -749,7 +745,7 @@ public class ContactService implements GeckoEventListener { types.put(type); phone.put("type", types); phone.put("carrier", cursor.getString(cursor.getColumnIndex(CARRIER_COLUMN))); - phone.put("pref", bool(cursor.getInt(cursor.getColumnIndex(Phone.IS_SUPER_PRIMARY)))); + phone.put("pref", cursor.getInt(cursor.getColumnIndex(Phone.IS_SUPER_PRIMARY))); phones.put(phone); } @@ -802,7 +798,7 @@ public class ContactService implements GeckoEventListener { address.put("postalCode", postalCode); types.put(type); address.put("type", types); - address.put("pref", bool(cursor.getInt(cursor.getColumnIndex(StructuredPostal.IS_SUPER_PRIMARY)))); + address.put("pref", cursor.getInt(cursor.getColumnIndex(StructuredPostal.IS_SUPER_PRIMARY))); addresses.put(address); } @@ -972,7 +968,7 @@ public class ContactService implements GeckoEventListener { } } - private void insertContact(final JSONObject contactProperties, final String requestID) throws JSONException { + private void insertContact(final JSONObject contactProperties, final String requestID) { ArrayList newContactOptions = new ArrayList(); // Account to save the contact under @@ -1018,7 +1014,7 @@ public class ContactService implements GeckoEventListener { new Object[] {newRawContactId, "create"}); } - private void updateContact(final JSONObject contactProperties, final long rawContactId, final String requestID) throws JSONException { + private void updateContact(final JSONObject contactProperties, final long rawContactId, final String requestID) { // Why is updating a contact so weird and horribly inefficient? Because Android doesn't // like multiple values for contact fields, but the Mozilla contacts API calls for this. // This means the Android update function is essentially completely useless. Why not just @@ -1069,61 +1065,65 @@ public class ContactService implements GeckoEventListener { new Object[] {rawContactId, "update"}); } - private List getContactValues(final JSONObject contactProperties) throws JSONException { + private List getContactValues(final JSONObject contactProperties) { List contactValues = new ArrayList(); - // Add the contact to the default group so it is shown in other apps - // like the Contacts or People app - ContentValues defaultGroupValues = new ContentValues(); - defaultGroupValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); - defaultGroupValues.put(GroupMembership.GROUP_ROW_ID, mGroupId); - contactValues.add(defaultGroupValues); + try { + // Add the contact to the default group so it is shown in other apps + // like the Contacts or People app + ContentValues defaultGroupValues = new ContentValues(); + defaultGroupValues.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); + defaultGroupValues.put(GroupMembership.GROUP_ROW_ID, mGroupId); + contactValues.add(defaultGroupValues); - // Create all the values that will be inserted into the new contact - getNameValues(contactProperties.optJSONArray("name"), - contactProperties.optJSONArray("givenName"), - contactProperties.optJSONArray("familyName"), - contactProperties.optJSONArray("honorificPrefix"), - contactProperties.optJSONArray("honorificSuffix"), - contactValues); + // Create all the values that will be inserted into the new contact + getNameValues(contactProperties.optJSONArray("name"), + contactProperties.optJSONArray("givenName"), + contactProperties.optJSONArray("familyName"), + contactProperties.optJSONArray("honorificPrefix"), + contactProperties.optJSONArray("honorificSuffix"), + contactValues); - getGenericValues(MIMETYPE_ADDITIONAL_NAME, CUSTOM_DATA_COLUMN, - contactProperties.optJSONArray("additionalName"), contactValues); + getGenericValues(MIMETYPE_ADDITIONAL_NAME, CUSTOM_DATA_COLUMN, + contactProperties.optJSONArray("additionalName"), contactValues); - getNicknamesValues(contactProperties.optJSONArray("nickname"), contactValues); + getNicknamesValues(contactProperties.optJSONArray("nickname"), contactValues); - getAddressesValues(contactProperties.optJSONArray("adr"), contactValues); + getAddressesValues(contactProperties.optJSONArray("adr"), contactValues); - getPhonesValues(contactProperties.optJSONArray("tel"), contactValues); + getPhonesValues(contactProperties.optJSONArray("tel"), contactValues); - getEmailsValues(contactProperties.optJSONArray("email"), contactValues); + getEmailsValues(contactProperties.optJSONArray("email"), contactValues); - //getPhotosValues(contactProperties.optJSONArray("photo"), contactValues); + //getPhotosValues(contactProperties.optJSONArray("photo"), contactValues); - getGenericValues(Organization.CONTENT_ITEM_TYPE, Organization.COMPANY, - contactProperties.optJSONArray("org"), contactValues); + getGenericValues(Organization.CONTENT_ITEM_TYPE, Organization.COMPANY, + contactProperties.optJSONArray("org"), contactValues); - getGenericValues(Organization.CONTENT_ITEM_TYPE, Organization.TITLE, - contactProperties.optJSONArray("jobTitle"), contactValues); + getGenericValues(Organization.CONTENT_ITEM_TYPE, Organization.TITLE, + contactProperties.optJSONArray("jobTitle"), contactValues); - getNotesValues(contactProperties.optJSONArray("note"), contactValues); + getNotesValues(contactProperties.optJSONArray("note"), contactValues); - getWebsitesValues(contactProperties.optJSONArray("url"), contactValues); + getWebsitesValues(contactProperties.optJSONArray("url"), contactValues); - getImsValues(contactProperties.optJSONArray("impp"), contactValues); + getImsValues(contactProperties.optJSONArray("impp"), contactValues); - getCategoriesValues(contactProperties.optJSONArray("category"), contactValues); + getCategoriesValues(contactProperties.optJSONArray("category"), contactValues); - getEventValues(contactProperties.optString("bday"), Event.TYPE_BIRTHDAY, contactValues); + getEventValues(contactProperties.optString("bday"), Event.TYPE_BIRTHDAY, contactValues); - getEventValues(contactProperties.optString("anniversary"), Event.TYPE_ANNIVERSARY, contactValues); + getEventValues(contactProperties.optString("anniversary"), Event.TYPE_ANNIVERSARY, contactValues); - getCustomMimetypeValues(contactProperties.optString("sex"), MIMETYPE_SEX, contactValues); + getCustomMimetypeValues(contactProperties.optString("sex"), MIMETYPE_SEX, contactValues); - getCustomMimetypeValues(contactProperties.optString("genderIdentity"), MIMETYPE_GENDER_IDENTITY, contactValues); + getCustomMimetypeValues(contactProperties.optString("genderIdentity"), MIMETYPE_GENDER_IDENTITY, contactValues); - getGenericValues(MIMETYPE_KEY, CUSTOM_DATA_COLUMN, contactProperties.optJSONArray("key"), - contactValues); + getGenericValues(MIMETYPE_KEY, CUSTOM_DATA_COLUMN, contactProperties.optJSONArray("key"), + contactValues); + } catch (JSONException e) { + throw new IllegalArgumentException("Unexpected or missing JSON data: " + e); + } return contactValues; } @@ -1239,7 +1239,7 @@ public class ContactService implements GeckoEventListener { } if (address.has("pref")) { - contentValues.put(Data.IS_SUPER_PRIMARY, address.getBoolean("pref") ? 1 : 0); + contentValues.put(Data.IS_SUPER_PRIMARY, address.getInt("pref")); } return contentValues; @@ -1263,7 +1263,7 @@ public class ContactService implements GeckoEventListener { final int typeConstant = getPhoneType(type); contentValues = createContentValues(Phone.CONTENT_ITEM_TYPE, phone.optString("value"), - typeConstant, type, phone.optBoolean("pref")); + typeConstant, type, phone.optInt("pref")); if (phone.has("carrier")) { contentValues.put(CARRIER_COLUMN, phone.optString("carrier")); } @@ -1271,7 +1271,7 @@ public class ContactService implements GeckoEventListener { } } else { contentValues = createContentValues(Phone.CONTENT_ITEM_TYPE, phone.optString("value"), - -1, null, phone.optBoolean("pref")); + -1, null, phone.optInt("pref")); if (phone.has("carrier")) { contentValues.put(CARRIER_COLUMN, phone.optString("carrier")); } @@ -1299,12 +1299,12 @@ public class ContactService implements GeckoEventListener { newContactValues.add(createContentValues(Email.CONTENT_ITEM_TYPE, email.optString("value"), typeConstant, type, - email.optBoolean("pref"))); + email.optInt("pref"))); } } else { newContactValues.add(createContentValues(Email.CONTENT_ITEM_TYPE, email.optString("value"), - -1, null, email.optBoolean("pref"))); + -1, null, email.optInt("pref"))); } } } @@ -1349,12 +1349,12 @@ public class ContactService implements GeckoEventListener { newContactValues.add(createContentValues(Website.CONTENT_ITEM_TYPE, website.optString("value"), typeConstant, type, - website.optBoolean("pref"))); + website.optInt("pref"))); } } else { newContactValues.add(createContentValues(Website.CONTENT_ITEM_TYPE, website.optString("value"), - -1, null, website.optBoolean("pref"))); + -1, null, website.optInt("pref"))); } } } @@ -1378,12 +1378,12 @@ public class ContactService implements GeckoEventListener { newContactValues.add(createContentValues(Im.CONTENT_ITEM_TYPE, im.optString("value"), typeConstant, type, - im.optBoolean("pref"))); + im.optInt("pref"))); } } else { newContactValues.add(createContentValues(Im.CONTENT_ITEM_TYPE, im.optString("value"), - -1, null, im.optBoolean("pref"))); + -1, null, im.optInt("pref"))); } } } @@ -1463,11 +1463,11 @@ public class ContactService implements GeckoEventListener { } private ContentValues createContentValues(final String mimeType, final String value, final int typeConstant, - final String type, final boolean preferredValue) { + final String type, final int preferredValue) { ContentValues contentValues = new ContentValues(); contentValues.put(Data.MIMETYPE, mimeType); contentValues.put(Data.DATA1, value); - contentValues.put(Data.IS_SUPER_PRIMARY, preferredValue ? 1 : 0); + contentValues.put(Data.IS_SUPER_PRIMARY, preferredValue); if (type != null) { contentValues.put(Data.DATA2, typeConstant); diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 263b5a49fe4d..cafba2972ac6 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -135,6 +135,7 @@ @BINPATH@/components/dom_camera.xpt @BINPATH@/components/dom_canvas.xpt @BINPATH@/components/dom_core.xpt +@BINPATH@/components/dom_contacts.xpt @BINPATH@/components/dom_css.xpt @BINPATH@/components/dom_devicestorage.xpt @BINPATH@/components/dom_events.xpt diff --git a/mobile/android/modules/ContactService.jsm b/mobile/android/modules/ContactService.jsm index aa00175a7269..84cc10813444 100644 --- a/mobile/android/modules/ContactService.jsm +++ b/mobile/android/modules/ContactService.jsm @@ -64,15 +64,7 @@ let ContactService = { debug("observe: subject: " + aSubject + " topic: " + aTopic + " data: " + aData); } - let message = JSON.parse(aData, function date_reviver(k, v) { - // The Java service sends dates as strings, so convert them to Dates before - // sending them back to the child. - if (v != null && v != "null" && - ["updated", "published", "anniversary", "bday"].indexOf(k) != -1) { - return new Date(v); - } - return v; - }); + let message = JSON.parse(aData); let requestID = message.requestID; // The return message topic is the same as the current topic, but without the "Android:" prefix