From e3d4aa1b0f70fac300be079e0d95d3a3dab871a4 Mon Sep 17 00:00:00 2001 From: Reuben Morais Date: Sun, 29 Sep 2013 22:22:34 -0300 Subject: [PATCH] Bug 850430 - Convert the Contacts API to WebIDL. r=bz r=gwagner --- dom/contacts/ContactManager.js | 832 ++++++++---------- dom/contacts/ContactManager.manifest | 11 - dom/contacts/fallback/ContactDB.jsm | 88 +- dom/contacts/tests/test_contacts_basics.html | 323 ++++--- dom/contacts/tests/test_contacts_blobs.html | 70 +- dom/contacts/tests/test_contacts_getall.html | 224 +++-- .../tests/test_contacts_international.html | 10 +- .../test_contacts_substringmatching.html | 12 +- .../test_contacts_substringmatchingVE.html | 6 +- dom/icc/interfaces/nsIDOMIccManager.idl | 3 +- dom/icc/interfaces/nsIIccProvider.idl | 3 +- dom/icc/src/IccManager.cpp | 2 +- dom/icc/tests/marionette/test_icc_contact.js | 16 +- dom/interfaces/contacts/moz.build | 4 +- .../contacts/nsIContactProperties.idl | 74 -- .../contacts/nsIDOMContactManager.idl | 41 - dom/system/gonk/RILContentHelper.js | 3 +- dom/webidl/Contacts.webidl | 170 ++++ dom/webidl/moz.build | 1 + 19 files changed, 876 insertions(+), 1017 deletions(-) delete mode 100644 dom/interfaces/contacts/nsIContactProperties.idl delete mode 100644 dom/interfaces/contacts/nsIDOMContactManager.idl create mode 100644 dom/webidl/Contacts.webidl diff --git a/dom/contacts/ContactManager.js b/dom/contacts/ContactManager.js index 64a6816ef29..68054a397e9 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.defineLazyGetter(Services, "DOMRequest", function() { - return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService); -}); +XPCOMUtils.defineLazyServiceGetter(Services, "DOMRequest", + "@mozilla.org/dom/dom-request-service;1", + "nsIDOMRequestService"); XPCOMUtils.defineLazyServiceGetter(this, "pm", "@mozilla.org/permissionmanager;1", @@ -29,325 +29,196 @@ XPCOMUtils.defineLazyServiceGetter(this, "cpmm", const CONTACTS_SENDMORE_MINIMUM = 5; -function stringOrBust(aObj) { - if (typeof aObj != "string") { - if (DEBUG) debug("Field is not a string and was ignored."); - return undefined; - } else { - return aObj; - } -} - -function sanitizeStringArray(aArray) { - if (!Array.isArray(aArray)) { - aArray = [aArray]; - } - return aArray.map(stringOrBust).filter(function(el) { return el != undefined; }); -} - -const nsIClassInfo = Ci.nsIClassInfo; -const CONTACTPROPERTIES_CID = Components.ID("{35ad8a4e-9486-44b6-883d-550f14635e49}"); -const nsIContactProperties = Ci.nsIContactProperties; - -// 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 ContactAddress() { } ContactAddress.prototype = { - __exposedProps__: { - type: 'rw', - streetAddress: 'rw', - locality: 'rw', - region: 'rw', - postalCode: 'rw', - countryName: 'rw', - pref: 'rw' - }, + // 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; + }, - 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, + 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; + }, - QueryInterface : XPCOMUtils.generateQI([nsIContactAddress]) -} - -//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; + classID: Components.ID("{9cbfa81c-bcab-4ca9-b0d2-f4318f295e33}"), + contractID: "@mozilla.org/contactAddress;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), }; +function ContactField() { } + ContactField.prototype = { - __exposedProps__: { - type: 'rw', - value: 'rw', - pref: 'rw' - }, + // 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 : 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, + pref: this.pref, + }; + if (!excludeExposedProps) { + json.__exposedProps__ = { + type: "rw", + value: "rw", + pref: "rw", + }; + } + return json; + }, - QueryInterface : XPCOMUtils.generateQI([nsIContactField]) -} - -//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; + classID: Components.ID("{ad19a543-69e4-44f0-adfa-37c011556bc1}"), + contractID: "@mozilla.org/contactField;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), }; +function ContactTelField() { } + ContactTelField.prototype = { - __exposedProps__: { - type: 'rw', - value: 'rw', - carrier: 'rw', - pref: 'rw' - }, + // 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 : CONTACTTELFIELD_CID, - classInfo : XPCOMUtils.generateCI({classID: CONTACTTELFIELD_CID, - contractID: CONTACTTELFIELD_CONTRACTID, - classDescription: "ContactTelField", - interfaces: [nsIContactTelField], - 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([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]) + classID: Components.ID("{4d42c5a9-ea5d-4102-80c3-40cc986367ca}"), + contractID: "@mozilla.org/contactTelField;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), }; -//ContactFindOptions +function validateArrayField(data, createCb) { + function isVanillaObj(aObj) { + return Object.prototype.toString.call(aObj) == "[object Object]"; + } -const CONTACTFINDOPTIONS_CONTRACTID = "@mozilla.org/contactFindOptions;1"; -const CONTACTFINDOPTIONS_CID = Components.ID("{28ce07d0-45d9-4b7a-8843-521df4edd8bc}"); -const nsIContactFindOptions = Components.interfaces.nsIContactFindOptions; + // We use an array-like Proxy to validate data set by content, since we don't + // have WebIDL arrays yet. See bug 851726. -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; + // 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 (!(aBlob[i] instanceof Components.interfaces.nsIDOMBlob)) { - return null; + if (name === "length") { + return "r"; } } - return aBlob; - } - return null; -} + }); -function isVanillaObj(aObj) { - return Object.prototype.toString.call(aObj) == "[object Object]"; -} + 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]; + } + }; -function validateArrayField(data, createCb) { if (data) { data = Array.isArray(data) ? data : [data]; let filtered = []; for (let obj of data) { - if (obj && isVanillaObj(obj)) { + if (isVanillaObj(obj)) { filtered.push(createCb(obj)); } } - return filtered; + if (filtered.length === 0) { + return undefined; + } + return new Proxy(filtered, ArrayPropertyHandler); } return undefined; } -function Contact() { }; +// 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() { } Contact.prototype = { - __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; - }, - + // 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. set email(aEmail) { this._email = validateArrayField(aEmail, function(email) { - return new ContactField(email.type, email.value, email.pref); - }); + let obj = new this._window.ContactField(); + obj.initialize(email.type, email.value, email.pref); + return obj; + }.bind(this)); }, get email() { @@ -356,10 +227,12 @@ Contact.prototype = { set 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); - }); + let obj = new this._window.ContactAddress(); + obj.initialize(adr.type, adr.streetAddress, adr.locality, + adr.region, adr.postalCode, adr.countryName, + adr.pref); + return obj; + }.bind(this)); }, get adr() { @@ -368,8 +241,10 @@ Contact.prototype = { set tel(aTel) { this._tel = validateArrayField(aTel, function(tel) { - return new ContactTelField(tel.type, tel.value, tel.carrier, tel.pref); - }); + let obj = new this._window.ContactTelField(); + obj.initialize(tel.type, tel.value, tel.carrier, tel.pref); + return obj; + }.bind(this)); }, get tel() { @@ -378,8 +253,10 @@ Contact.prototype = { set impp(aImpp) { this._impp = validateArrayField(aImpp, function(impp) { - return new ContactField(impp.type, impp.value, impp.pref); - }); + let obj = new this._window.ContactField(); + obj.initialize(impp.type, impp.value, impp.pref); + return obj; + }.bind(this)); }, get impp() { @@ -388,95 +265,21 @@ Contact.prototype = { set url(aUrl) { this._url = validateArrayField(aUrl, function(url) { - return new ContactField(url.type, url.value, url.pref); - }); + let obj = new this._window.ContactField(); + obj.initialize(url.type, url.value, url.pref); + return obj; + }.bind(this)); }, get url() { return this._url; }, - set org(aOrg) { - this._org = sanitizeStringArray(aOrg); + init: function(aWindow) { + this._window = aWindow; }, - 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) { + __init: function(aProp) { this.name = aProp.name; this.honorificPrefix = aProp.honorificPrefix; this.givenName = aProp.givenName; @@ -501,76 +304,105 @@ Contact.prototype = { this.key = aProp.key; }, - get published () { - return this._published; + setMetadata: function(aId, aPublished, aUpdated) { + this.id = aId; + if (aPublished) { + this.published = aPublished; + } + if (aUpdated) { + this.updated = aUpdated; + } }, - set published(aPublished) { - this._published = aPublished; + 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", + } + }; }, - get updated () { - return this._updated; - }, + classID: Components.ID("{72a5ee28-81d8-4af8-90b3-ae935396cc66}"), + contractID: "@mozilla.org/contact;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, + Ci.nsIDOMGlobalPropertyInitializer]), +}; - 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"); -} +function ContactManager() { } ContactManager.prototype = { __proto__: DOMRequestIpcHelper.prototype, - _oncontactchange: null, + hasListenPermission: false, _cachedContacts: [] , - 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); + set oncontactchange(aHandler) { + this.__DOM_IMPL__.setEventHandler("oncontactchange", aHandler); }, get oncontactchange() { - return this._oncontactchange; + return this.__DOM_IMPL__.getEventHandler("oncontactchange"); }, - _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); + _convertContact: function(aContact) { + if (aContact.properties.bday) { + aContact.properties.bday = new Date(aContact.properties.bday); + } + if (aContact.properties.anniversary) { + aContact.properties.anniversary = new Date(aContact.properties.anniversary); + } + let newContact = new this._window.mozContact(aContact.properties); + newContact.setMetadata(aContact.id, aContact.published, aContact.updated); return newContact; }, @@ -680,13 +512,11 @@ ContactManager.prototype = { case "Contact:Changed": // Fire oncontactchange event if (DEBUG) debug("Contacts:ContactChanged: " + msg.contactID + ", " + msg.reason); - if (this._oncontactchange) { - let event = new this._window.MozContactChangeEvent("contactchanged", { - contactID: msg.contactID, - reason: msg.reason - }); - this._oncontactchange.handleEvent(event); - } + let event = new this._window.MozContactChangeEvent("contactchange", { + contactID: msg.contactID, + reason: msg.reason + }); + this.dispatchEvent(event); break; case "Contacts:Revision": if (DEBUG) debug("new revision: " + msg.revision); @@ -708,6 +538,12 @@ 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; @@ -765,35 +601,53 @@ ContactManager.prototype = { }, save: function save(aContact) { - 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]; + // 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]; + } } + + for (let prop of ADDRESS_PROPERTIES) { + if (aContact[prop]) { + newContact.properties[prop] = []; + for (let i of aContact[prop]) { + if (i) { + let json = ContactAddress.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 = ContactField.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 = ContactTelField.prototype.toJSON.apply(i, [true]); + newContact.properties[prop].push(json); + } + } + } + } + let request = this.createRequest(); let requestID = this.getRequestId({request: request, reason: reason}); @@ -801,7 +655,7 @@ ContactManager.prototype = { if (aContact.id == "undefined") { // for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes // 25c00f0190e5c545b4d421E2ddbab9e0 - aContact.id = this._getRandomId().replace('-', '', 'g').replace('{', '').replace('}', ''); + aContact.id = this._getRandomId().replace(/[{}-]/g, ""); // Cache the contact so that its ID may be updated later if necessary this._cachedContacts[requestID] = aContact; reason = "create"; @@ -809,8 +663,12 @@ ContactManager.prototype = { reason = "update"; } - this._setMetaData(newContact, aContact); + newContact.id = aContact.id; + newContact.published = aContact.published; + newContact.updated = aContact.updated; + if (DEBUG) debug("send: " + JSON.stringify(newContact)); + let options = { contact: newContact, reason: reason }; let allowCallback = function() { cpmm.sendAsyncMessage("Contact:Save", {requestID: requestID, options: options}); @@ -886,19 +744,18 @@ 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; - request = this.createRequest(); + let 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; }, @@ -938,6 +795,7 @@ 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", @@ -947,26 +805,22 @@ 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); }, - // Called from DOMRequestIpcHelper - uninit: function uninit() { - if (DEBUG) debug("uninit call"); - if (this._oncontactchange) - this._oncontactchange = null; - }, + classID: Components.ID("{8beb3a66-d70a-4111-b216-b8e995ad3aff}"), + contractID: "@mozilla.org/contactManager;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, + Ci.nsIDOMGlobalPropertyInitializer]), +}; - 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]) +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ + Contact, ContactManager, ContactAddress, ContactField, ContactTelField +]); diff --git a/dom/contacts/ContactManager.manifest b/dom/contacts/ContactManager.manifest index 20484b62563..a0938fe90eb 100644 --- a/dom/contacts/ContactManager.manifest +++ b/dom/contacts/ContactManager.manifest @@ -1,6 +1,3 @@ -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} @@ -10,16 +7,8 @@ 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 793772f8bff..7a8323599f1 100644 --- a/dom/contacts/fallback/ContactDB.jsm +++ b/dom/contacts/fallback/ContactDB.jsm @@ -20,7 +20,7 @@ Cu.import("resource://gre/modules/IndexedDBHelper.jsm"); Cu.import("resource://gre/modules/PhoneNumberUtils.jsm"); const DB_NAME = "contacts"; -const DB_VERSION = 14; +const DB_VERSION = 15; const STORE_NAME = "contacts"; const SAVED_GETALL_STORE_NAME = "getallcache"; const CHUNK_SIZE = 20; @@ -28,16 +28,10 @@ const REVISION_STORE = "revision"; const REVISION_KEY = "revision"; function exportContact(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; + if (aRecord) { + delete aRecord.search; + } + return aRecord; } function ContactDispatcher(aContacts, aFullContacts, aCallback, aNewTxn, aClearDispatcher, aFailureCb) { @@ -150,9 +144,7 @@ ContactDB.prototype = { for (let i = 0; i < contacts.length; i++) { let contact = {}; contact.properties = contacts[i]; - contact.id = idService.generateUUID().toString().replace('-', '', 'g') - .replace('{', '') - .replace('}', ''); + contact.id = idService.generateUUID().toString().replace(/[{}-]/g, ""); contact = this.makeImport(contact); this.updateRecordMetadata(contact); if (DEBUG) debug("import: " + JSON.stringify(contact)); @@ -549,6 +541,47 @@ 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; @@ -576,31 +609,7 @@ ContactDB.prototype = { }, makeImport: function makeImport(aContact) { - 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: [], - }; + let contact = {properties: {}}; contact.search = { givenName: [], @@ -687,7 +696,6 @@ ContactDB.prototype = { } } } - if (DEBUG) debug("contact:" + JSON.stringify(contact)); contact.updated = aContact.updated; contact.published = aContact.published; diff --git a/dom/contacts/tests/test_contacts_basics.html b/dom/contacts/tests/test_contacts_basics.html index 662e87424c8..04fca7806cc 100644 --- a/dom/contacts/tests/test_contacts_basics.html +++ b/dom/contacts/tests/test_contacts_basics.html @@ -61,29 +61,30 @@ var c4 = { }; var c5 = { - nickname: "empty" + familyName: [], + givenName: [], }; 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", @@ -92,7 +93,7 @@ var adr1 = { }; var adr2 = { - type: "home, fax", + type: ["home, fax"], streetAddress: "street2", locality: "locality2", region: "region2", @@ -104,34 +105,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; @@ -166,20 +167,39 @@ 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) { - // comparing /[null(,null)+]/ and undefined should pass - function nonNull(e) { - return e != null; + function normalize_falsy(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); + function optArray(val) { + return Array.isArray(val) ? val : [val]; } + 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) { @@ -188,44 +208,31 @@ function checkAddress(adr1, adr2) { 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"); + 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"); 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"); - checkStrArray(field1.value, field2.value, "Same value"); + checkStr(field1.value, field2.value, "Same value"); checkPref(field1.pref, field2.pref); } -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; - } +function checkTel(tel1, tel2) { + if (tel1 ^ tel2) { + ok(false, "Expected both tels to be either present or absent"); + return; } - is(pref1, pref2, "Same pref"); + checkField(tel1, tel2); + checkStr(tel1.carrier, tel2.carrier, "Same carrier"); } function checkCategory(category1, category2) { @@ -239,21 +246,42 @@ function checkCategory(category1, category2) { } function removeAndroidDefaultCategory(category) { - if (category == undefined) { - return; + if (!category) { + return category; } - for (var i = 0; i < category.length; i++) { + var result = []; + + for (var i of category) { // Some devices may return the full group name (prefixed with "System Group: ") - if (category[i] == "My Contacts" || category[i] == "System Group: My Contacts") { - category.splice(i, 1); + if (i != "My Contacts" && i != "System Group: My Contacts") { + result.push(i); } } - return category; + 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 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"); @@ -271,21 +299,11 @@ function checkContacts(contact1, contact2) { checkStr(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity"); checkStrArray(contact1.key, contact2.key, "Same key"); - 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]); - } + 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"); } var req; @@ -293,6 +311,10 @@ var index = 0; var initialRev; +var defaultOptions = { + sortBy: "givenName", +}; + function checkRevision(revision, msg, then) { var revReq = mozContacts.getRevision(); revReq.onsuccess = function(e) { @@ -319,8 +341,6 @@ 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(); @@ -354,7 +374,7 @@ var steps = [ }, function () { ok(true, "Retrieving all contacts"); - req = mozContacts.find({}); + req = mozContacts.find(defaultOptions); req.onsuccess = function () { is(req.result.length, 0, "Empty database."); checkRevision(1, "Revision was not incremented on find", next); @@ -363,8 +383,7 @@ var steps = [ }, function () { ok(true, "Adding empty contact"); - createResult1 = new mozContact(); - createResult1.init({}); + createResult1 = new mozContact({}); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -377,7 +396,7 @@ var steps = [ }, function () { ok(true, "Retrieving all contacts"); - req = mozContacts.find({}); + req = mozContacts.find(defaultOptions); req.onsuccess = function () { is(req.result.length, 1, "One contact."); findResult1 = req.result[0]; @@ -389,7 +408,7 @@ var steps = [ ok(true, "Deleting empty contact"); req = navigator.mozContacts.remove(findResult1); req.onsuccess = function () { - var req2 = mozContacts.find({}); + var req2 = mozContacts.find(defaultOptions); req2.onsuccess = function () { is(req2.result.length, 0, "Empty Database."); clearTemps(); @@ -401,8 +420,7 @@ var steps = [ }, function () { ok(true, "Adding a new contact1"); - createResult1 = new mozContact(); - createResult1.init(properties1); + createResult1 = new mozContact(properties1); mozContacts.oncontactchange = function(event) { is(event.contactID, createResult1.id, "Same contactID"); @@ -487,8 +505,7 @@ var steps = [ is(event.contactID, createResult2.id, "Same contactID"); is(event.reason, "create", "Same reason"); } - createResult2 = new mozContact(); - createResult2.init({name: "newName"}); + createResult2 = new mozContact({name: ["newName"]}); req = navigator.mozContacts.save(createResult2); req.onsuccess = function () { ok(createResult2.id, "The contact now has an ID."); @@ -573,8 +590,7 @@ var steps = [ }, function () { ok(true, "Adding a new contact with properties1"); - createResult1 = new mozContact(); - createResult1.init(properties1); + createResult1 = new mozContact(properties1); mozContacts.oncontactchange = null; req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { @@ -788,14 +804,16 @@ var steps = [ }, function () { ok(true, "Retrieving all contacts"); - req = mozContacts.find({}); + req = mozContacts.find(defaultOptions); 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); - ok(findResult1.updated, "Has updated field"); - ok(findResult1.published, "Has published field"); + if (!isAndroid) { + ok(findResult1.updated, "Has updated field"); + ok(findResult1.published, "Has published field"); + } next(); } req.onerror = onFailure; @@ -808,7 +826,7 @@ var steps = [ findResult1.impp = properties1.impp = [{value:"phil impp"}]; req = navigator.mozContacts.save(findResult1); req.onsuccess = function () { - var req2 = mozContacts.find({}); + var req2 = mozContacts.find(defaultOptions); req2.onsuccess = function() { is(req2.result.length, 1, "Found exactly 1 contact."); findResult2 = req2.result[0]; @@ -872,7 +890,7 @@ var steps = [ findResult1.impp = properties1.impp = [{value: "phil impp"}]; req = mozContacts.save(findResult1); req.onsuccess = function () { - var req2 = mozContacts.find({}); + var req2 = mozContacts.find(defaultOptions); req2.onsuccess = function () { is(req2.result.length, 1, "Found exactly 1 contact."); findResult1 = req2.result[0]; @@ -937,9 +955,8 @@ var steps = [ SpecialPowers.executeSoon(next); } else { findResult1.email = [{value: properties1.nickname}]; - findResult1.nickname = "TEST"; - var newContact = new mozContact(); - newContact.init(findResult1); + findResult1.nickname = ["TEST"]; + var newContact = new mozContact(findResult1); req = mozContacts.save(newContact); req.onsuccess = function () { var options = {filterBy: ["email", "givenName"], @@ -961,7 +978,7 @@ var steps = [ ok(true, "Deleting contact" + findResult1); req = mozContacts.remove(findResult1); req.onsuccess = function () { - var req2 = mozContacts.find({}); + var req2 = mozContacts.find(defaultOptions); req2.onsuccess = function () { is(req2.result.length, 1, "One contact left."); findResult1 = req2.result[0]; @@ -982,8 +999,7 @@ var steps = [ }, function () { ok(true, "Adding a new contact"); - createResult1 = new mozContact(); - createResult1.init(properties1); + createResult1 = new mozContact(properties1); req = mozContacts.save(createResult1) req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -994,8 +1010,7 @@ var steps = [ }, function () { ok(true, "Adding a new contact2"); - createResult2 = new mozContact(); - createResult2.init(properties2); + createResult2 = new mozContact(properties2); req = mozContacts.save(createResult2); req.onsuccess = function () { ok(createResult2.id, "The contact now has an ID."); @@ -1006,10 +1021,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(properties2, req.result[1]); + checkContacts(req.result[1], properties1); next(); } req.onerror = onFailure; @@ -1086,16 +1101,14 @@ var steps = [ function () { ok(true, "Adding 20 contacts"); for (var i=0; i<19; i++) { - createResult1 = new mozContact(); - createResult1.init(properties1); + createResult1 = new mozContact(properties1); req = mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); }; req.onerror = onFailure; }; - createResult1 = new mozContact(); - createResult1.init(properties1); + createResult1 = new mozContact(properties1); req = mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1106,7 +1119,7 @@ var steps = [ }, function () { ok(true, "Retrieving all contacts"); - req = mozContacts.find({}); + req = mozContacts.find(defaultOptions); req.onsuccess = function () { is(req.result.length, 20, "20 Entries."); next(); @@ -1152,11 +1165,10 @@ var steps = [ ok(true, "Retrieving all contacts3"); var options = {filterBy: ["givenName", "tel", "email"], filterOp: "startsWith", - filterValue: properties1.givenName[0].substring(0, 4), - filterLimit: 15 }; + filterValue: properties1.givenName[0].substring(0, 4)}; req = mozContacts.find(options); req.onsuccess = function () { - is(req.result.length, 15, "15 Entries."); + is(req.result.length, 20, "20 Entries."); checkContacts(createResult1, req.result[10]); next(); } @@ -1173,8 +1185,7 @@ var steps = [ }, function () { ok(true, "Testing clone contact"); - createResult1 = new mozContact(); - createResult1.init(properties1); + createResult1 = new mozContact(properties1); req = mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1187,14 +1198,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."); - ok(cloned.email[0].value == "new email!", "Same Email"); - ok(createResult1.email != cloned.email, "Clone has different email"); - ok(cloned.givenName == "Tom", "New Name"); + 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"); next(); } req.onerror = onFailure; @@ -1204,7 +1215,7 @@ var steps = [ var options = {filterBy: ["givenName"], filterOp: "startsWith", filterValue: properties2.givenName[0].substring(0, 4)}; - req = mozContacts.find({}); + req = mozContacts.find(defaultOptions); req.onsuccess = function () { is(req.result.length, 2, "2 Entries."); next(); @@ -1213,11 +1224,14 @@ var steps = [ }, function () { ok(true, "Search with redundant fields should only return 1 contact"); - createResult1 = new mozContact(); - createResult1.init({name: "XXX", givenName: "XXX", email: [{value: "XXX"}], tel: {value: "XXX"}}); + createResult1 = new mozContact({name: ["XXX"], + givenName: ["XXX"], + email: [{value: "XXX"}], + tel: [{value: "XXX"}] + }); req = mozContacts.save(createResult1); req.onsuccess = function() { - var options = {filterBy: [], + var options = {filterBy: ["givenName", "familyName"], filterOp: "equals", filterValue: "XXX"}; var req2 = mozContacts.find(options); @@ -1240,8 +1254,7 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(); - createResult1.init(c3); + createResult1 = new mozContact(c3); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1252,8 +1265,7 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(); - createResult1.init(c2); + createResult1 = new mozContact(c2); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1264,8 +1276,7 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(); - createResult1.init(c4); + createResult1 = new mozContact(c4); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1276,8 +1287,7 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(); - createResult1.init(c1); + createResult1 = new mozContact(c1); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1318,8 +1328,7 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(); - createResult1.init(c5); + createResult1 = new mozContact(c5); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1346,17 +1355,16 @@ var steps = [ }, function () { ok(true, "Don't allow to add custom fields"); - createResult1 = new mozContact(); - createResult1.init({givenName: "customTest", yyy: "XXX"}); + createResult1 = new mozContact({givenName: ["customTest"], yyy: "XXX"}); req = mozContacts.save(createResult1); req.onsuccess = function() { - var options = {filterBy: [], + var options = {filterBy: ["givenName"], filterOp: "equals", filterValue: "customTest"}; var req2 = mozContacts.find(options); req2.onsuccess = function() { is(req2.result.length, 1, "1 Entry"); - checkStrArray(req2.result.givenName, "customTest", "same name"); + checkStrArray(req2.result[0].givenName, ["customTest"], "same name"); ok(req2.result.yyy === undefined, "custom property undefined"); next(); } @@ -1375,8 +1383,7 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(); - createResult1.init(c7); + createResult1 = new mozContact(c7); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1387,8 +1394,7 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(); - createResult1.init(c6); + createResult1 = new mozContact(c6); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1399,8 +1405,7 @@ var steps = [ }, function () { ok(true, "Test sorting"); - createResult1 = new mozContact(); - createResult1.init(c8); + createResult1 = new mozContact(c8); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1430,7 +1435,7 @@ var steps = [ }, function () { ok(true, "Deleting database"); - req = mozContacts.clear() + req = mozContacts.clear(); req.onsuccess = function () { ok(true, "Deleted the database"); next(); @@ -1439,8 +1444,7 @@ var steps = [ }, function () { ok(true, "Adding a new contact with properties2"); - createResult2 = new mozContact(); - createResult2.init(properties2); + createResult2 = new mozContact(properties2); req = mozContacts.save(createResult2); req.onsuccess = function () { ok(createResult2.id, "The contact now has an ID."); @@ -1485,9 +1489,8 @@ var steps = [ req.onerror = onFailure; }, function () { - ok(true, "Adding empty contact"); - createResult1 = new mozContact(); - createResult1.init({name: "5", givenName: "5"}); + ok(true, "Adding contact for category search"); + createResult1 = new mozContact({name: ["5"], givenName: ["5"]}); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1522,20 +1525,10 @@ 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"}, - additionalName: 7, - nickname: [8, 9], - org: [10, 11], - jobTitle: [12, 13], - note: 14, - category: [15, 16], + honorificSuffix: [{foo: "bar"}], sex: 17, genderIdentity: 18, - key: 4, email: input, adr: input, tel: input, @@ -1553,12 +1546,12 @@ var steps = [ } } })()); - createResult1 = new mozContact(); - createResult1.init(obj); + createResult1 = new mozContact(obj); req = mozContacts.save(createResult1); req.onsuccess = function () { checkContacts(createResult1, { - honorificPrefix: "string", + honorificPrefix: ["string"], + honorificSuffix: ["[object Object]"], sex: "17", genderIdentity: "18" }); @@ -1567,8 +1560,7 @@ var steps = [ }, function () { ok(true, "Adding contact with no number but carrier"); - createResult1 = new mozContact(); - createResult1.init({ tel: [{type: ["home"], carrier: "myCarrier"} ] }); + createResult1 = new mozContact({ tel: [{type: ["home"], carrier: "myCarrier"} ] }); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1578,8 +1570,7 @@ var steps = [ }, function () { ok(true, "Adding contact with email but no value"); - createResult1 = new mozContact(); - createResult1.init({ email: [{type: ["home"]}] }); + createResult1 = new mozContact({ email: [{type: ["home"]}] }); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1589,8 +1580,7 @@ var steps = [ }, function () { ok(true, "Testing numbersOnly search 1"); - createResult1 = new mozContact(); - createResult1.init({ name: ["aaaaaaaaa"], givenName: ["aaaaaaaaa"], tel: [{ value: "1234567890"}]}); + createResult1 = new mozContact({ name: ["aaaaaaaaa"], givenName: ["aaaaaaaaa"], tel: [{ value: "1234567890"}]}); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -1670,17 +1660,10 @@ 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 494218edcc7..559ea927000 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,8 +177,7 @@ var steps = [ }, function () { ok(true, "Adding contact with photo"); - createResult1 = new mozContact(); - createResult1.init(properties1); + createResult1 = new mozContact(properties1); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -191,7 +190,7 @@ var steps = [ ok(true, "Retrieving by substring"); var options = {filterBy: ["givenName"], filterOp: "startsWith", - filterValue: properties1.givenName.substring(0,3)}; + filterValue: properties1.givenName[0].substring(0,3)}; req = mozContacts.find(options); req.onsuccess = function () { ok(req.result.length == 1, "Found exactly 1 contact."); @@ -203,8 +202,7 @@ var steps = [ }, function () { ok(true, "Adding contact with 2 photos"); - createResult1 = new mozContact(); - createResult1.init(properties2); + createResult1 = new mozContact(properties2); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -217,7 +215,7 @@ var steps = [ ok(true, "Retrieving by substring"); var options = {filterBy: ["givenName"], filterOp: "startsWith", - filterValue: properties2.givenName.substring(0,3)}; + filterValue: properties2.givenName[0].substring(0,3)}; req = mozContacts.find(options); req.onsuccess = function () { ok(req.result.length == 1, "Found exactly 1 contact."); @@ -227,58 +225,6 @@ 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 acc0d62a477..daccff0f25a 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,102 +58,137 @@ function onFailure() { ok(false, "in on Failure!"); next(); } - function checkStr(str1, str2, msg) { - // comparing /[null(,null)+]/ and undefined should pass - function nonNull(e) { - return e != null; + if (str1 ^ str2) { + ok(false, "Expected both strings to be either present or absent"); + return; } - 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); + is(str1, str2, msg); +} + +function checkStrArray(str1, str2, msg) { + function normalize_falsy(k, v) { + if (!v || v == "null" || v == "undefined") { + return ""; + } + return v; } + 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) { - 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) { - checkStr(tel1.type, tel2.type, "Same type"); - checkStr(tel1.value, tel2.value, "Same value"); - checkStr(tel1.carrier, tel2.carrier, "Same carrier"); + 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) { - checkStr(field1.type, field2.type, "Same type"); - checkStr(field1.value, field2.value, "Same value"); + 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); +} + +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"); +} + +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 checkContacts(contact1, contact2) { - 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"); + 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"); is(contact1.bday ? contact1.bday.valueOf() : null, contact2.bday ? contact2.bday.valueOf() : null, "Same birthday"); - checkStr(contact1.note, contact2.note, "Same note"); + checkStrArray(contact1.note, contact2.note, "Same note"); is(contact1.anniversary ? contact1.anniversary.valueOf() : null , contact2.anniversary ? contact2.anniversary.valueOf() : null, "Same anniversary"); - is(contact1.sex, contact2.sex, "Same sex"); - is(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity"); + checkStr(contact1.sex, contact2.sex, "Same sex"); + checkStr(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity"); + checkStrArray(contact1.key, contact2.key, "Same key"); - 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]); - } + 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"); } function clearDatabase() { @@ -169,24 +204,22 @@ 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.init(properties1); + properties1.name = [properties1.givenName[0] + " " + properties1.familyName[0]]; + createResult1 = new mozContact(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.init(properties1); + properties1.name = [properties1.givenName[0] + " Testname39"]; + createResult1 = new mozContact(properties1); req = mozContacts.save(createResult1); req.onsuccess = function() { ok(createResult1.id, "The contact now has an ID."); - ok(createResult1.name == properties1.name, "Same Name"); + checkStrArray(createResult1.name, properties1.name, "Same Name"); next(); }; req.onerror = onFailure; @@ -202,6 +235,7 @@ 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"); @@ -246,11 +280,14 @@ 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.init({}); + createResult1 = new mozContact({}); req = navigator.mozContacts.save(createResult1); req.onsuccess = function() { next(); @@ -441,6 +478,7 @@ 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 358aa52126d..4b14bd03248 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,8 +81,7 @@ var steps = [ }, function () { ok(true, "Adding a new contact1"); - createResult1 = new mozContact(); - createResult1.init(properties1); + createResult1 = new mozContact(properties1); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -93,8 +92,7 @@ var steps = [ }, function () { ok(true, "Adding a new contact2"); - var createResult2 = new mozContact(); - createResult2.init(properties2); + var createResult2 = new mozContact(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 b5fea828a9c..c71d1a2639d 100644 --- a/dom/contacts/tests/test_contacts_substringmatching.html +++ b/dom/contacts/tests/test_contacts_substringmatching.html @@ -82,8 +82,7 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(); - createResult1.init(prop); + createResult1 = new mozContact(prop); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -166,8 +165,7 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(); - createResult1.init(prop2); + createResult1 = new mozContact(prop2); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -259,8 +257,7 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(); - createResult1.init(prop3); + createResult1 = new mozContact(prop3); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -289,8 +286,7 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(); - createResult1.init(prop4); + createResult1 = new mozContact(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 0b226a490da..484abb9afbb 100644 --- a/dom/contacts/tests/test_contacts_substringmatchingVE.html +++ b/dom/contacts/tests/test_contacts_substringmatchingVE.html @@ -73,8 +73,7 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(); - createResult1.init(prop); + createResult1 = new mozContact(prop); req = navigator.mozContacts.save(createResult1); req.onsuccess = function () { ok(createResult1.id, "The contact now has an ID."); @@ -112,8 +111,7 @@ var steps = [ }, function () { ok(true, "Adding contact"); - createResult1 = new mozContact(); - createResult1.init(prop2); + createResult1 = new mozContact(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 0102e788f24..d5ca00829e1 100644 --- a/dom/icc/interfaces/nsIDOMIccManager.idl +++ b/dom/icc/interfaces/nsIDOMIccManager.idl @@ -5,7 +5,6 @@ #include "nsIDOMEventTarget.idl" #include "SimToolKit.idl" -interface nsIDOMContact; interface nsIDOMDOMRequest; interface nsIDOMEventListener; interface nsIDOMMozIccInfo; @@ -507,7 +506,7 @@ interface nsIDOMMozIccManager : nsIDOMEventTarget * PIN2 is only required for 'fdn'. */ nsIDOMDOMRequest updateContact(in DOMString contactType, - in nsIDOMContact contact, + in nsISupports 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 ac98ca81220..0620a98e22a 100644 --- a/dom/icc/interfaces/nsIIccProvider.idl +++ b/dom/icc/interfaces/nsIIccProvider.idl @@ -4,7 +4,6 @@ #include "nsISupports.idl" -interface nsIDOMContact; interface nsIDOMDOMRequest; interface nsIDOMMozIccInfo; interface nsIDOMWindow; @@ -74,7 +73,7 @@ interface nsIIccProvider : nsISupports nsIDOMDOMRequest updateContact(in nsIDOMWindow window, in DOMString contactType, - in nsIDOMContact contact, + in nsISupports contact, in DOMString pin2); /** diff --git a/dom/icc/src/IccManager.cpp b/dom/icc/src/IccManager.cpp index 92709dfe8cd..7e302dda3a9 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, - nsIDOMContact* aContact, + nsISupports* 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 ef5ac8c928c..b7c75c7c83c 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, "Mozilla"); + is(contacts[0].name[0], "Mozilla"); is(contacts[0].tel[0].value, "15555218201"); is(contacts[0].id, "890141032111185107201"); - is(contacts[1].name, "Saßê黃"); + is(contacts[1].name[0], "Saßê黃"); is(contacts[1].tel[0].value, "15555218202"); is(contacts[1].id, "890141032111185107202"); - is(contacts[2].name, "Fire 火"); + is(contacts[2].name[0], "Fire 火"); is(contacts[2].tel[0].value, "15555218203"); is(contacts[2].id, "890141032111185107203"); - is(contacts[3].name, "Huang 黃"); + is(contacts[3].name[0], "Huang 黃"); is(contacts[3].tel[0].value, "15555218204"); is(contacts[3].id, "890141032111185107204"); @@ -41,10 +41,8 @@ function testReadContacts(type) { }; function testAddContact(type, pin2) { - let contact = new mozContact(); - - contact.init({ - name: "add", + let contact = new mozContact({ + name: ["add"], tel: [{value: "0912345678"}], email:[] }); @@ -62,7 +60,7 @@ function testAddContact(type, pin2) { // There are 4 SIM contacts which are harded in emulator is(contacts.length, 5); - is(contacts[4].name, "add"); + is(contacts[4].name[0], "add"); is(contacts[4].tel[0].value, "0912345678"); runNextTest(); diff --git a/dom/interfaces/contacts/moz.build b/dom/interfaces/contacts/moz.build index d7fc401f3fe..23404155753 100644 --- a/dom/interfaces/contacts/moz.build +++ b/dom/interfaces/contacts/moz.build @@ -5,9 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. XPIDL_SOURCES += [ - 'nsIContactProperties.idl', - 'nsIDOMContactManager.idl', - 'nsIDOMMozContactChangeEvent.idl', + 'nsIDOMMozContactChangeEvent.idl' ] XPIDL_MODULE = 'dom_contacts' diff --git a/dom/interfaces/contacts/nsIContactProperties.idl b/dom/interfaces/contacts/nsIContactProperties.idl deleted file mode 100644 index e5fe19f4153..00000000000 --- a/dom/interfaces/contacts/nsIContactProperties.idl +++ /dev/null @@ -1,74 +0,0 @@ -/* 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 deleted file mode 100644 index 5ae8a3d3162..00000000000 --- a/dom/interfaces/contacts/nsIDOMContactManager.idl +++ /dev/null @@ -1,41 +0,0 @@ -/* 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/system/gonk/RILContentHelper.js b/dom/system/gonk/RILContentHelper.js index bb7a390d1a9..07ebd7210f1 100644 --- a/dom/system/gonk/RILContentHelper.js +++ b/dom/system/gonk/RILContentHelper.js @@ -1671,7 +1671,6 @@ 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) { @@ -1684,7 +1683,7 @@ RILContentHelper.prototype = { prop.tel.push({value: c.anr[i]}); } - contact.init(prop); + let contact = new window.mozContact(prop); contact.id = message.iccid + c.recordId; return contact; }); diff --git a/dom/webidl/Contacts.webidl b/dom/webidl/Contacts.webidl new file mode 100644 index 00000000000..ab704c943f8 --- /dev/null +++ b/dom/webidl/Contacts.webidl @@ -0,0 +1,170 @@ +/* -*- 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, optional Date published, optional 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; +}; + +[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/moz.build b/dom/webidl/moz.build index 6fb447e30f2..3d40287a3be 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -53,6 +53,7 @@ WEBIDL_FILES = [ 'CommandEvent.webidl', 'Comment.webidl', 'CompositionEvent.webidl', + 'Contacts.webidl', 'ConvolverNode.webidl', 'Coordinates.webidl', 'CSS.webidl',