Backed out 8 changesets (bug 916267, bug 850430) for mochitest failures
authorReuben Morais <reuben.morais@gmail.com>
Mon, 30 Sep 2013 01:44:06 -0300
changeset 149214 d09f86ec45cde70e2199368bc38d669b715120b8
parent 149213 b85ed93dee3b409e1b69c9f6de0cd98ba57cca1f
child 149215 de0be3ff99cf118eddf4b801470ce0a7b4308623
child 149242 044255192754a5fad12d4b30f9683f0c393c9c7c
push id2881
push useremorley@mozilla.com
push dateMon, 30 Sep 2013 16:21:19 +0000
treeherderfx-team@c088ab1e2d8b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs916267, 850430
milestone27.0a1
Backed out 8 changesets (bug 916267, bug 850430) for mochitest failures
CLOBBER
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
dom/contacts/ContactManager.js
dom/contacts/ContactManager.manifest
dom/contacts/fallback/ContactDB.jsm
dom/contacts/tests/test_contacts_basics.html
dom/contacts/tests/test_contacts_blobs.html
dom/contacts/tests/test_contacts_getall.html
dom/contacts/tests/test_contacts_international.html
dom/contacts/tests/test_contacts_substringmatching.html
dom/contacts/tests/test_contacts_substringmatchingVE.html
dom/icc/interfaces/nsIDOMIccManager.idl
dom/icc/interfaces/nsIIccProvider.idl
dom/icc/src/IccManager.cpp
dom/icc/tests/marionette/test_icc_contact.js
dom/interfaces/contacts/moz.build
dom/interfaces/contacts/nsIContactProperties.idl
dom/interfaces/contacts/nsIDOMContactManager.idl
dom/interfaces/contacts/nsIDOMMozContactChangeEvent.idl
dom/moz.build
dom/system/gonk/RILContentHelper.js
dom/webidl/Contacts.webidl
dom/webidl/MozContactChangeEvent.webidl
dom/webidl/moz.build
js/xpconnect/src/event_impl_gen.conf.in
mobile/android/base/ContactService.java
mobile/android/installer/package-manifest.in
mobile/android/modules/ContactService.jsm
--- a/CLOBBER
+++ b/CLOBBER
@@ -12,9 +12,10 @@
 #          O               O
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # 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.
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -172,16 +172,17 @@
 @BINPATH@/components/dom_cellbroadcast.xpt
 @BINPATH@/components/dom_wappush.xpt
 #endif
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #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
 @BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -182,16 +182,17 @@
 @BINPATH@/components/dom_icc.xpt
 @BINPATH@/components/dom_wappush.xpt
 #endif
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #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
 @BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
--- a/dom/contacts/ContactManager.js
+++ b/dom/contacts/ContactManager.js
@@ -10,280 +10,478 @@ function debug(s) { dump("-*- ContactMan
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 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",
                                    "nsIPermissionManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
 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;
+  }
+}
+
+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;
 
-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;
-  },
+// 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;
 
-  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;
-  },
+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;
+};
+
+ContactAddress.prototype = {
+  __exposedProps__: {
+                      type: 'rw',
+                      streetAddress: 'rw',
+                      locality: 'rw',
+                      region: 'rw',
+                      postalCode: 'rw',
+                      countryName: 'rw',
+                      pref: 'rw'
+                     },
 
-  classID: Components.ID("{9cbfa81c-bcab-4ca9-b0d2-f4318f295e33}"),
-  contractID: "@mozilla.org/contactAddress;1",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
+  classID : CONTACTADDRESS_CID,
+  classInfo : XPCOMUtils.generateCI({classID: CONTACTADDRESS_CID,
+                                     contractID: CONTACTADDRESS_CONTRACTID,
+                                     classDescription: "ContactAddress",
+                                     interfaces: [nsIContactAddress],
+                                     flags: nsIClassInfo.DOM_OBJECT}),
+
+  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;
 };
 
-function ContactFieldImpl() { }
+ContactField.prototype = {
+  __exposedProps__: {
+                      type: 'rw',
+                      value: '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 : 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
 
-  classID: Components.ID("{ad19a543-69e4-44f0-adfa-37c011556bc1}"),
-  contractID: "@mozilla.org/contactField;1",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
+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;
 };
 
-function ContactTelFieldImpl() { }
+ContactTelField.prototype = {
+  __exposedProps__: {
+                      type: 'rw',
+                      value: 'rw',
+                      carrier: '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 : 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;
 
-  classID: Components.ID("{4d42c5a9-ea5d-4102-80c3-40cc986367ca}"),
-  contractID: "@mozilla.org/contactTelField;1",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
+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])
 };
 
-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.
+//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}),
 
-  // 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";
+  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 (name === "length") {
-        return "r";
+      if (!(aBlob[i] instanceof Components.interfaces.nsIDOMBlob)) {
+        return null;
       }
     }
-  });
+    return aBlob;
+  }
+  return null;
+}
 
-  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 isVanillaObj(aObj) {
+  return Object.prototype.toString.call(aObj) == "[object Object]";
+}
 
+function validateArrayField(data, createCb) {
   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);
+  },
+
+  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;
   },
 
-  __init: function(aProp) {
+  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;
     this.additionalName =  aProp.additionalName;
     this.familyName =      aProp.familyName;
     this.honorificSuffix = aProp.honorificSuffix;
     this.nickname =        aProp.nickname;
     this.email =           aProp.email;
@@ -298,109 +496,86 @@ Contact.prototype = {
     this.note =            aProp.note;
     this.impp =            aProp.impp;
     this.anniversary =     aProp.anniversary;
     this.sex =             aProp.sex;
     this.genderIdentity =  aProp.genderIdentity;
     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;
+  },
+
+  set published(aPublished) {
+    this._published = aPublished;
+  },
+
+  get updated () {
+    return this._updated;
+  },
+
+  set updated(aUpdated) {
+    this._updated = aUpdated;
   },
 
-  toJSON: function() {
-    return {
-      id:              this.id,
-      published:       this.published,
-      updated:         this.updated,
+  classID : CONTACT_CID,
+  classInfo : XPCOMUtils.generateCI({classID: CONTACT_CID,
+                                     contractID: CONTACT_CONTRACTID,
+                                     classDescription: "Contact",
+                                     interfaces: [nsIDOMContact, nsIContactProperties],
+                                     flags: nsIClassInfo.DOM_OBJECT}),
 
-      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,
+  QueryInterface : XPCOMUtils.generateQI([nsIDOMContact, nsIContactProperties])
+}
 
-      __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",
-      }
-    };
-  },
+// ContactManager
 
-  classID: Components.ID("{72a5ee28-81d8-4af8-90b3-ae935396cc66}"),
-  contractID: "@mozilla.org/contact;1",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
-                                         Ci.nsIDOMGlobalPropertyInitializer]),
-};
+const CONTACTMANAGER_CONTRACTID = "@mozilla.org/contactManager;1";
+const CONTACTMANAGER_CID        = Components.ID("{8beb3a66-d70a-4111-b216-b8e995ad3aff}");
+const nsIDOMContactManager      = Components.interfaces.nsIDOMContactManager;
 
-function ContactManager() { }
+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;
   },
 
   _convertContacts: function(aContacts) {
     let contacts = [];
     for (let i in aContacts) {
       contacts.push(this._convertContact(aContacts[i]));
     }
@@ -500,21 +675,23 @@ ContactManager.prototype = {
           req.allow();
         } else {
           req.cancel();
         }
         break;
       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);
         req = this.getRequest(msg.requestID);
         if (req) {
           Services.DOMRequest.fireSuccess(req.request, msg.revision);
         }
         break;
@@ -526,22 +703,16 @@ ContactManager.prototype = {
         }
         break;
       default:
         if (DEBUG) debug("Wrong message: " + aMessage.name);
     }
     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;
     switch(aAccess) {
       case "create":
         access = "create";
         break;
       case "update":
@@ -589,84 +760,62 @@ ContactManager.prototype = {
       origin: principal.origin,
       appID: principal.appId,
       browserFlag: principal.isInBrowserElement,
       windowID: this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID
     });
   },
 
   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];
-      }
-    }
-
-    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);
-          }
-        }
-      }
+    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 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});
 
     let reason;
     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";
     } else {
       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});
     }.bind(this)
     this.askPermission(reason, request, allowCallback);
     return request;
   },
 
@@ -732,28 +881,29 @@ ContactManager.prototype = {
     if (!aRecord || !aRecord.id) {
       Services.DOMRequest.fireErrorAsync(request, true);
       return request;
     }
 
     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;
   },
 
   getRevision: function() {
     let request = this.createRequest();
 
     let allowCallback = function() {
@@ -783,37 +933,40 @@ ContactManager.prototype = {
       Services.DOMRequest.fireError(request);
     };
 
     this.askPermission("count", request, allowCallback, cancelCallback);
     return request;
   },
 
   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",
                               "Contact:Remove:Return:OK", "Contact:Remove:Return:KO",
                               "Contact:Changed",
                               "PermissionPromptHelper:AskPermission:OK",
                               "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]),
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
-  Contact, ContactManager, ContactFieldImpl, ContactAddressImpl, ContactTelFieldImpl
-]);
+  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])
--- a/dom/contacts/ContactManager.manifest
+++ b/dom/contacts/ContactManager.manifest
@@ -1,14 +1,25 @@
+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}
 
 component {ad19a543-69e4-44f0-adfa-37c011556bc1} ContactManager.js
 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
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -15,38 +15,34 @@ const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 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) {
+  let contact = {};
+  contact.properties = aRecord.properties;
 
-function exportContact(aRecord) {
-  if (aRecord) {
-    delete aRecord.search;
-  }
-  return aRecord;
+  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) {
   let nextIndex = 0;
 
   let sendChunk;
   let count = 0;
   if (aFullContacts) {
@@ -149,17 +145,19 @@ ContactDB.prototype = {
       }
 
       let idService = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
       objectStore = aTransaction.objectStore(STORE_NAME);
 
       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));
         objectStore.put(contact);
       }
     }.bind(this);
 
     function createFinalSchema() {
@@ -546,57 +544,16 @@ ContactDB.prototype = {
             if (modified || modified2) {
               cursor.update(cursor.value);
             }
           } else {
             next();
           }
         };
       },
-      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;
     let outer = this;
     function next() {
       if (index == aNewVersion) {
         outer.incrementRevision(aTransaction);
         return;
@@ -614,17 +571,41 @@ ContactDB.prototype = {
     if (aNewVersion > steps.length) {
       dump("Contacts DB upgrade error!");
       aTransaction.abort();
     }
     next();
   },
 
   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:       [],
       familyName:      [],
       email:           [],
       category:        [],
       tel:             [],
       exactTel:        [],
@@ -701,19 +682,20 @@ ContactDB.prototype = {
               if (typeof val == "string") {
                 contact.search[field].push(val.toLowerCase());
               }
             }
           }
         }
       }
     }
+    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;
   },
 
   updateRecordMetadata: function updateRecordMetadata(record) {
     if (!record.id) {
       Cu.reportError("Contact without ID");
--- a/dom/contacts/tests/test_contacts_basics.html
+++ b/dom/contacts/tests/test_contacts_basics.html
@@ -56,88 +56,87 @@ var c3 = {
 
 var c4 = {
   name: ["c c", "a a", "c c"],
   familyName: ["c","a","c"],
   givenName: ["c","a","c"],
 };
 
 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",
   postalCode: "postal code 1",
   countryName: "country 1"
 };
 
 var adr2 = {
-  type: ["home, fax"],
+  type: "home, fax",
   streetAddress: "street2",
   locality: "locality2",
   region: "region2",
   postalCode: "postal code2",
   countryName: "country2"
 };
 
 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;
 var sample_id2;
 
 var createResult1;
 var createResult2;
 
@@ -162,131 +161,104 @@ function onFailure() {
   next();
 }
 
 function checkStr(str1, str2, msg) {
   if (str1 ^ str2) {
     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;
-  }
-  function optArray(val) {
-    return Array.isArray(val) ? val : [val];
+  // comparing /[null(,null)+]/ and undefined should pass
+  function nonNull(e) {
+    return e != null;
   }
-  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;
-    }
+  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(!!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");
-  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) {
   // 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;
+  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");
   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);
@@ -294,32 +266,38 @@ function checkContacts(contact1, contact
   checkStrArray(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");
   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");
 
-  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;
 var index = 0;
 
 var initialRev;
 
-var defaultOptions = {
-  sortBy: "givenName",
-};
-
 function checkRevision(revision, msg, then) {
   var revReq = mozContacts.getRevision();
   revReq.onsuccess = function(e) {
     is(e.target.result, initialRev+revision, msg);
     then();
   };
   // The revision function isn't supported on Android so treat on failure as success
   if (isAndroid) {
@@ -336,16 +314,18 @@ function checkCount(count, msg, then) {
   request.onsuccess = function(e) {
     is(e.target.result, count, msg);
     then();
   };
   request.onerror = onFailure;
 }
 
 var mozContacts = window.navigator.mozContacts;
+ok(mozContacts, "mozContacts exists");
+ok("mozContact" in window, "mozContact exists");
 var steps = [
   function() {
     req = mozContacts.getRevision();
     req.onsuccess = function(e) {
       initialRev = e.target.result;
       next();
     };
 
@@ -369,63 +349,65 @@ var steps = [
           checkRevision(1, "Revision was incremented on clear", next);
         });
       };
       req.onerror = onFailure;
     });
   },
   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);
     };
     req.onerror = onFailure;
   },
   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.");
       sample_id1 = createResult1.id;
       checkCount(1, "1 contact after adding empty contact", function() {
         checkRevision(2, "Revision was incremented on save", next);
       });
     };
     req.onerror = onFailure;
   },
   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];
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     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();
         checkRevision(3, "Revision was incremented on remove", next);
       }
       req2.onerror = onFailure;
     }
     req.onerror = onFailure;
   },
   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");
       is(event.reason, "create", "Same reason");
       next();
     }
 
     req = navigator.mozContacts.save(createResult1);
@@ -500,17 +482,18 @@ var steps = [
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact");
     mozContacts.oncontactchange = function(event) {
        is(event.contactID, createResult2.id, "Same contactID");
        is(event.reason, "create", "Same reason");
      }
-    createResult2 = new mozContact({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.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
@@ -585,17 +568,18 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     };
     req.onerror = onFailure;
   },
   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 () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       checkContacts(createResult1, properties1);
       next();
     };
@@ -799,39 +783,37 @@ var steps = [
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
     req.onerror = onFailure;
   },
   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;
   },
   function () {
     ok(true, "Modifying contact1");
     if (!findResult1) {
       SpecialPowers.executeSoon(next);
     } else {
       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];
           ok(findResult2.id == sample_id1, "Same ID");
           checkContacts(findResult2, properties1);
           is(findResult2.impp.length, 1, "Found exactly 1 IMS info.");
           next();
         };
@@ -885,17 +867,17 @@ var steps = [
   function () {
     ok(true, "Modifying contact2");
     if (!findResult1) {
       SpecialPowers.executeSoon(next);
     } else {
       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];
           ok(findResult1.id == sample_id1, "Same ID");
           checkContacts(findResult1, properties1);
           is(findResult1.impp.length, 1, "Found exactly 1 IMS info.");
           next();
         }
@@ -950,18 +932,19 @@ var steps = [
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Modifying contact3");
     if (!findResult1) {
       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"],
                        filterOp: "startsWith",
                        filterValue: properties1.givenName[0]};
         // One contact has it in nickname and the other in email
         var req2 = mozContacts.find(options);
         req2.onsuccess = function () {
@@ -973,17 +956,17 @@ var steps = [
       };
       req.onerror = onFailure;
     }
   },
   function () {
     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];
         next();
       }
       req2.onerror = onFailure;
     }
     req.onerror = onFailure;
@@ -994,42 +977,44 @@ var steps = [
     req.onsuccess =  function () {
       clearTemps();
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       sample_id1 = createResult1.id;
       next();
     }
     req.onerror = onFailure;
   },
   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.");
       sample_id2 = createResult2.id;
       next();
     };
     req.onerror = onFailure;
   },
   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;
   },
   function () {
     console.log("Searching contacts by query1");
     var options = {filterBy: ["givenName", "email"],
                    filterOp: "startsWith",
@@ -1096,35 +1081,37 @@ var steps = [
       clearTemps();
       next();
     }
     req.onerror = onFailure;
   },
   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.");
       checkStrArray(createResult1.name, properties1.name, "Same Name");
       checkCount(20, "20 contacts in DB", next);
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts");
-    req = mozContacts.find(defaultOptions);
+    req = mozContacts.find({});
     req.onsuccess = function () {
       is(req.result.length, 20, "20 Entries.");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts with limit 10");
@@ -1160,83 +1147,82 @@ var steps = [
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     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();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Deleting database");
     req = mozContacts.clear();
     req.onsuccess = function () {
       clearTemps();
       next();
     }
     req.onerror = onFailure;
   },
   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.");
       checkStrArray(createResult1.name, properties1.name, "Same Name");
       next();
     }
     req.onerror = onFailure;
   },
   function() {
     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;
   },
   function () {
     ok(true, "Retrieving all contacts");
     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();
     }
     req.onerror = onFailure;
   },
   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);
       req2.onsuccess = function() {
         is(req2.result.length, 1, "1 Entry");
         next();
       }
       req2.onerror = onFailure;
@@ -1249,50 +1235,54 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   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.");
       checkContacts(c3, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       checkContacts(c2, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       checkContacts(c4, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       checkContacts(c1, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
@@ -1323,17 +1313,18 @@ var steps = [
       checkContacts(req.result[2], c2);
       checkContacts(req.result[3], c1);
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       checkContacts(c5, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
@@ -1350,26 +1341,27 @@ var steps = [
       checkContacts(req.result[3], c3);
       checkContacts(req.result[4], c4);
       next();
     };
     req.onerror = onFailure;
   },
   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();
       }
       req2.onerror = onFailure;
     }
     req.onerror = onFailure;
   },
   function () {
@@ -1378,39 +1370,42 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   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.");
       checkContacts(c7, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       checkContacts(c6, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       checkContacts(c8, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
@@ -1430,26 +1425,27 @@ var steps = [
       ok(req.result[0].published < req.result[1].published, "Right sorting order");
       ok(req.result[1].published < req.result[2].published, "Right sorting order");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Deleting database");
-    req = mozContacts.clear();
+    req = mozContacts.clear()
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   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.");
       sample_id2 = createResult2.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -1484,18 +1480,19 @@ var steps = [
     req = mozContacts.clear()
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     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.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -1520,20 +1517,30 @@ var steps = [
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     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,
         impp: input,
         url: input
     };
     obj.honorificPrefix.__defineGetter__('0',(function() {
       var c = 0;
@@ -1541,51 +1548,54 @@ var steps = [
         if (c == 0) {
           c++;
           return "string";
         } else {
           return {foo:"bar"};
         }
       }
     })());
-    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"
       });
       next();
     };
   },
   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.");
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
@@ -1655,20 +1665,27 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   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();
   },
   function () {
     ok(true, "all done!\n");
     clearTemps();
 
--- a/dom/contacts/tests/test_contacts_blobs.html
+++ b/dom/contacts/tests/test_contacts_blobs.html
@@ -85,24 +85,24 @@ function verifyBuffers(buffer1, buffer2,
   if (isLast)
     next();
 }
 
 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]
 };
 
 var sample_id1;
 var createResult1;
 var findResult1;
 
 function onUnwantedSuccess() {
@@ -172,61 +172,115 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     };
     req.onerror = onFailure;
   },
   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.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName.substring(0,3)};
+    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");
+      verifyBlobArray(createResult1.photo, properties1.photo);
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact with 2 photos");
+    createResult1 = new mozContact();
+    createResult1.init(properties2);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring");
     var options = {filterBy: ["givenName"],
                    filterOp: "startsWith",
-                   filterValue: properties1.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.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
-      verifyBlobArray(createResult1.photo, properties1.photo);
+      verifyBlobArray(createResult1.photo, properties2.photo);
     };
     req.onerror = onFailure;
   },
   function () {
-    ok(true, "Adding contact with 2 photos");
-    createResult1 = new mozContact(properties2);
+    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: properties2.givenName[0].substring(0,3)};
+                   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");
-      verifyBlobArray(createResult1.photo, properties2.photo);
+      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()
     req.onsuccess = function () {
       ok(true, "Deleted the database");
--- a/dom/contacts/tests/test_contacts_getall.html
+++ b/dom/contacts/tests/test_contacts_getall.html
@@ -31,216 +31,182 @@ SpecialPowers.addPermission("contacts-re
 SpecialPowers.addPermission("contacts-create", true, document);
 
 var isAndroid = (navigator.userAgent.indexOf("Android") !== -1);
 var androidVersion = SpecialPowers.Cc['@mozilla.org/system-info;1']
                                   .getService(SpecialPowers.Ci.nsIPropertyBag2)
                                   .getProperty('version');
 
 let adr1 = {
-  type: ["work"],
+  type: "work",
   streetAddress: "street 1",
   locality: "locality 1",
   region: "region 1",
   postalCode: "postal code 1",
   countryName: "country 1"
 };
 
 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"}]
 };
 
 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");
-}
-
-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")
+  checkStr(tel1.type, tel2.type, "Same type");
+  checkStr(tel1.value, tel2.value, "Same value");
+  checkStr(tel1.carrier, tel2.carrier, "Same carrier");
 }
 
-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;
+  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");
+  checkStr(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");
+
+  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]);
   }
-  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");
-  checkStrArray(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");
-
-  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.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() {
   ok(true, "Clearing database");
   req = mozContacts.clear();
   req.onsuccess = function() {
     ok(true, "Cleared the database");
     next();
   };
   req.onerror = onFailure;
 }
 
 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;
 }
 
 let createResult1;
 
 let index = 0;
 let req;
 let mozContacts = window.navigator.mozContacts;
 
 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");
       if (req.result) {
         ok(true, "result is valid");
         count++;
         req.continue();
       } else {
@@ -275,24 +241,21 @@ function getAll(msg) {
         next();
       }
     };
     req.onerror = onFailure;
   }
 }
 
 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();
     };
     req.onerror = onFailure;
   },
 
   getOne(),
@@ -473,17 +436,16 @@ let steps = [
       next();
     }
   },
 
   clearDatabase,
 
   function() {
     ok(true, "all done!\n");
-    SpecialPowers.Cc["@mozilla.org/tools/profiler;1"].getService(SpecialPowers.Ci.nsIProfiler).AddMarker("GETALL_END");
     SimpleTest.finish();
   }
 ];
 
 function next() {
   ok(true, "Begin!");
   if (index >= steps.length) {
     ok(false, "Shouldn't get here!");
--- a/dom/contacts/tests/test_contacts_international.html
+++ b/dom/contacts/tests/test_contacts_international.html
@@ -46,23 +46,23 @@ var number1 = {
 };
 
 var number2 = {
   local: "7932012346",
   international: "+557932012346"
 };
 
 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"}]
 };
 
 var req;
 var index = 0;
 var createResult1;
 var findResult1;
 var sample_id1;
@@ -76,28 +76,30 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
--- a/dom/contacts/tests/test_contacts_substringmatching.html
+++ b/dom/contacts/tests/test_contacts_substringmatching.html
@@ -77,17 +77,18 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   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.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -160,17 +161,18 @@ var steps = [
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contacts.");
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -252,17 +254,18 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   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.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -281,17 +284,18 @@ var steps = [
       };
       req.onerror = onFailure;
     } else {
       SpecialPowers.executeSoon(next);
     }
   },
   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.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
--- a/dom/contacts/tests/test_contacts_substringmatchingVE.html
+++ b/dom/contacts/tests/test_contacts_substringmatchingVE.html
@@ -68,17 +68,18 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   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.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -106,17 +107,18 @@ var steps = [
       ok(findResult1.id == sample_id1, "Same ID");
       is(findResult1.tel[1].value, "7704143727591", "Same Value");
       next();
     };
     req.onerror = onFailure;
   },
   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.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
--- a/dom/icc/interfaces/nsIDOMIccManager.idl
+++ b/dom/icc/interfaces/nsIDOMIccManager.idl
@@ -1,15 +1,16 @@
 /* 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 "nsIDOMEventTarget.idl"
 #include "SimToolKit.idl"
 
+interface nsIDOMContact;
 interface nsIDOMDOMRequest;
 interface nsIDOMEventListener;
 interface nsIDOMMozIccInfo;
 
 [scriptable, builtinclass, uuid(904b92cb-dad3-416f-88cf-0291012cf448)]
 interface nsIDOMMozIccManager : nsIDOMEventTarget
 {
   /**
@@ -501,17 +502,17 @@ interface nsIDOMMozIccManager : nsIDOMEv
    *        - 'adn': Abbreviated Dialling Number
    *        - 'fdn': Fixed Dialling Number
    * @param contact
    *        The contact will be updated in ICC
    * @param [optional] pin2
    *        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.
 
   // UICC Secure Element Interfaces
 
   /**
    * A secure element is a smart card chip that can hold
--- a/dom/icc/interfaces/nsIIccProvider.idl
+++ b/dom/icc/interfaces/nsIIccProvider.idl
@@ -1,14 +1,15 @@
 /* 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 "nsISupports.idl"
 
+interface nsIDOMContact;
 interface nsIDOMDOMRequest;
 interface nsIDOMMozIccInfo;
 interface nsIDOMWindow;
 
 [scriptable, uuid(82d25440-c913-11e2-8b8b-0800200c9a66)]
 interface nsIIccListener : nsISupports
 {
   void notifyStkCommand(in DOMString aMessage);
@@ -68,17 +69,17 @@ interface nsIIccProvider : nsISupports
   /**
    * Phonebook interfaces.
    */
   nsIDOMDOMRequest readContacts(in nsIDOMWindow window,
                                 in DOMString contactType);
 
   nsIDOMDOMRequest updateContact(in nsIDOMWindow window,
                                  in DOMString contactType,
-                                 in nsISupports contact,
+                                 in nsIDOMContact contact,
                                  in DOMString pin2);
 
   /**
    * Secure Card Icc communication channel
    */
   nsIDOMDOMRequest iccOpenChannel(in nsIDOMWindow window,
                                   in DOMString aid);
 
--- a/dom/icc/src/IccManager.cpp
+++ b/dom/icc/src/IccManager.cpp
@@ -230,17 +230,17 @@ IccManager::ReadContacts(const nsAString
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->ReadContacts(GetOwner(), aContactType, aRequest);
 }
 
 NS_IMETHODIMP
 IccManager::UpdateContact(const nsAString& aContactType,
-                          nsISupports* aContact,
+                          nsIDOMContact* aContact,
                           const nsAString& aPin2,
                           nsIDOMDOMRequest** aRequest)
 {
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->UpdateContact(GetOwner(), aContactType, aContact, aPin2, aRequest);
--- a/dom/icc/tests/marionette/test_icc_contact.js
+++ b/dom/icc/tests/marionette/test_icc_contact.js
@@ -10,44 +10,46 @@ ok(icc instanceof MozIccManager, "icc is
 
 function testReadContacts(type) {
   let request = icc.readContacts(type);
   request.onsuccess = function onsuccess() {
     let contacts = request.result;
 
     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");
 
     runNextTest();
   };
 
   request.onerror = function onerror() {
     ok(false, "Cannot get " + type + " contacts");
     runNextTest();
   };
 };
 
 function testAddContact(type, pin2) {
-  let contact = new mozContact({
-    name: ["add"],
+  let contact = new mozContact();
+
+  contact.init({
+    name: "add",
     tel: [{value: "0912345678"}],
     email:[]
   });
 
   let updateRequest = icc.updateContact(type, contact, pin2);
 
   updateRequest.onsuccess = function onsuccess() {
     // Get ICC contact for checking new contact
@@ -55,17 +57,17 @@ function testAddContact(type, pin2) {
     let getRequest = icc.readContacts(type);
 
     getRequest.onsuccess = function onsuccess() {
       let contacts = getRequest.result;
 
       // 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();
     };
 
     getRequest.onerror = function onerror() {
       ok(false, "Cannot get " + type + " contacts: " + getRequest.error.name);
       runNextTest();
new file mode 100644
--- /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'
+
new file mode 100644
--- /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[]
+};
new file mode 100644
--- /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();
+};
new file mode 100644
--- /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;
+};
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -6,16 +6,17 @@
 
 interfaces = [
     'base',
     'canvas',
     'core',
     'html',
     'events',
     'devicestorage',
+    'contacts',
     'settings',
     'stylesheets',
     'sidebar',
     'css',
     'traversal',
     'range',
     'xbl',
     'xpath',
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -1666,29 +1666,30 @@ RILContentHelper.prototype = {
       this.fireRequestError(message.requestId, message.errorMsg);
       return;
     }
 
     let window = this._windowsMap[message.requestId];
     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) {
         prop.email = [{value: c.email}];
       }
 
       // ANR - Additional Number
       let anrLen = c.anr ? c.anr.length : 0;
       for (let i = 0; i < anrLen; i++) {
         prop.tel.push({value: c.anr[i]});
       }
 
-      let contact = new window.mozContact(prop);
+      contact.init(prop);
       contact.id = message.iccid + c.recordId;
       return contact;
     });
 
     this.fireRequestSuccess(message.requestId,
                             ObjectWrapper.wrap(result, window));
   },
 
deleted file mode 100644
--- 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<DOMString>? type,
-                  optional DOMString streetAddress,
-                  optional DOMString locality,
-                  optional DOMString region,
-                  optional DOMString postalCode,
-                  optional DOMString countryName,
-                  optional boolean pref);
-};
-
-dictionary ContactAddressInit {
-  sequence<DOMString>? 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<DOMString>? type,
-                  optional DOMString value,
-                  optional boolean pref);
-};
-
-dictionary ContactFieldInit {
-  sequence<DOMString>? type;
-  DOMString?           value;
-  boolean?             pref;
-};
-
-
-[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactTelField;1"]
-interface ContactTelField : ContactField {
-  attribute DOMString? carrier;
-
-  [ChromeOnly]
-  void initialize(optional sequence<DOMString>? 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<Blob>?                photo;
-
-  sequence<ContactAddressInit>?  adr;
-
-  sequence<ContactFieldInit>?    email;
-  sequence<ContactFieldInit>?    url;
-  sequence<ContactFieldInit>?    impp;
-
-  sequence<ContactTelFieldInit>? tel;
-
-  sequence<DOMString>?           name;
-  sequence<DOMString>?           honorificPrefix;
-  sequence<DOMString>?           givenName;
-  sequence<DOMString>?           additionalName;
-  sequence<DOMString>?           familyName;
-  sequence<DOMString>?           honorificSuffix;
-  sequence<DOMString>?           nickname;
-  sequence<DOMString>?           category;
-  sequence<DOMString>?           org;
-  sequence<DOMString>?           jobTitle;
-  sequence<DOMString>?           note;
-  sequence<DOMString>?           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;
-};
--- a/dom/webidl/MozContactChangeEvent.webidl
+++ b/dom/webidl/MozContactChangeEvent.webidl
@@ -1,15 +1,15 @@
 /* -*- 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/.
  */
 
-[Constructor(DOMString type, optional MozContactChangeEventInit eventInitDict)]
+[Constructor(DOMString type, optional MozContactChangeEventInit eventInitDict), HeaderFile="GeneratedEventClasses.h"]
 interface MozContactChangeEvent : Event
 {
   readonly attribute DOMString? contactID;
   readonly attribute DOMString? reason;
 };
 
 dictionary MozContactChangeEventInit : EventInit
 {
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -48,17 +48,16 @@ WEBIDL_FILES = [
     'CharacterData.webidl',
     'ChildNode.webidl',
     'ClientRect.webidl',
     'ClientRectList.webidl',
     'ClipboardEvent.webidl',
     'CommandEvent.webidl',
     'Comment.webidl',
     'CompositionEvent.webidl',
-    'Contacts.webidl',
     'ConvolverNode.webidl',
     'Coordinates.webidl',
     'CSS.webidl',
     'CSSPrimitiveValue.webidl',
     'CSSStyleDeclaration.webidl',
     'CSSStyleSheet.webidl',
     'CSSValue.webidl',
     'CSSValueList.webidl',
@@ -466,16 +465,17 @@ WEBIDL_FILES += [
     'CloseEvent.webidl',
     'CustomEvent.webidl',
     'DeviceOrientationEvent.webidl',
     'DeviceStorageChangeEvent.webidl',
     'DOMTransactionEvent.webidl',
     'ElementReplaceEvent.webidl',
     'HashChangeEvent.webidl',
     'MozApplicationEvent.webidl',
+    'MozContactChangeEvent.webidl',
     'MozMmsEvent.webidl',
     'MozSettingsEvent.webidl',
     'MozSmsEvent.webidl',
     'PageTransitionEvent.webidl',
     'PopStateEvent.webidl',
     'PopupBlockedEvent.webidl',
     'ProgressEvent.webidl',
     'RecordErrorEvent.webidl',
@@ -533,17 +533,16 @@ if CONFIG['ENABLE_TESTS']:
         'TestJSImplGen.webidl',
     ]
 
 GENERATED_EVENTS_WEBIDL_FILES = [
     'BlobEvent.webidl',
     'DeviceLightEvent.webidl',
     'DeviceProximityEvent.webidl',
     'MediaStreamEvent.webidl',
-    'MozContactChangeEvent.webidl',
     'MozInterAppMessageEvent.webidl',
     'RTCDataChannelEvent.webidl',
     'RTCPeerConnectionIceEvent.webidl',
     'UserProximityEvent.webidl',
 ]
 
 if CONFIG['MOZ_GAMEPAD']:
     GENERATED_EVENTS_WEBIDL_FILES += [
--- a/js/xpconnect/src/event_impl_gen.conf.in
+++ b/js/xpconnect/src/event_impl_gen.conf.in
@@ -12,16 +12,17 @@ simple_events = [
     'StorageEvent',
     'MozSettingsEvent',
     'CustomEvent',
     'PageTransitionEvent',
     'DOMTransactionEvent',
     'PopStateEvent',
     'HashChangeEvent',
     'CloseEvent',
+    'MozContactChangeEvent',
     'DeviceOrientationEvent',
     'MozApplicationEvent',
     'SmartCardEvent',
     'StyleRuleChangeEvent',
     'StyleSheetChangeEvent',
     'StyleSheetApplicableStateChangeEvent',
 #ifdef MOZ_B2G_BT
     'BluetoothDeviceEvent',
--- a/mobile/android/base/ContactService.java
+++ b/mobile/android/base/ContactService.java
@@ -209,20 +209,20 @@ public class ContactService implements G
         String sortBy = null;
         String sortOrder = null;
 
         try {
             final JSONObject findOptions = contactOptions.getJSONObject("findOptions");
             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";
             }
 
             // Only "familyname" and "givenname" are valid sortBy values and only "ascending"
             // and "descending" are valid sortOrder values
             if ((sortBy != null && !"familyname".equals(sortBy) && !"givenname".equals(sortBy)) ||
                 (!"ascending".equals(sortOrder) && !"descending".equals(sortOrder))) {
                 return null;
@@ -235,25 +235,25 @@ public class ContactService implements G
     }
 
     private long[] findContactsRawIds(final JSONObject contactOptions) {
         List<Long> rawContactIds = new ArrayList<Long>();
         Cursor cursor = null;
 
         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
                 if (filterLimit > 0 && allRawContactIds.length > filterLimit) {
                     long[] truncatedRawContactIds = new long[filterLimit];
                     for (int i = 0; i < filterLimit; i++) {
                         truncatedRawContactIds[i] = allRawContactIds[i];
                     }
@@ -508,17 +508,17 @@ public class ContactService implements G
         JSONArray notes = new JSONArray();
         JSONArray urls = new JSONArray();
         JSONArray impps = new JSONArray();
         JSONArray categories = new JSONArray();
         String bday = null;
         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();
 
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
         Uri entityUri = Uri.withAppendedPath(rawContactUri, Entity.CONTENT_DIRECTORY);
 
         Cursor cursor = mContentResolver.query(entityUri, columnsToGet, null, null, null);
@@ -609,17 +609,17 @@ public class ContactService implements G
 
                 } else if (MIMETYPE_SEX.equals(mimeType)) {
                     sex = cursor.getString(cursor.getColumnIndex(CUSTOM_DATA_COLUMN));
 
                 } else if (MIMETYPE_GENDER_IDENTITY.equals(mimeType)) {
                     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);
             }
         }
         cursor.close();
 
         try {
@@ -635,45 +635,41 @@ public class ContactService implements G
             contactProperties.put("tel", phones);
             contactProperties.put("email", emails);
             contactProperties.put("org", organizations);
             contactProperties.put("jobTitle", jobTitles);
             contactProperties.put("note", notes);
             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);
         }
 
         if (DEBUG) {
             try {
                 Log.d(LOGTAG, "Got contact: " + contact.toString(3));
             } catch (JSONException e) {}
         }
 
         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<String, Integer> typeMap) throws JSONException {
         String value = cursor.getString(cursor.getColumnIndex(dataColumn));
         int typeConstant = cursor.getInt(cursor.getColumnIndex(typeColumn));
         String type;
         if (typeConstant == BaseTypes.TYPE_CUSTOM) {
             type = cursor.getString(cursor.getColumnIndex(typeLabelColumn));
@@ -701,17 +697,17 @@ public class ContactService implements G
 
         // If an existing object wasn't found, make a new one
         if (!found) {
             JSONObject object = new JSONObject();
             JSONArray types = new JSONArray();
             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);
         }
     }
 
     private void getPhoneDataAsJSONObject(Cursor cursor, JSONArray phones) throws JSONException {
         String value = cursor.getString(cursor.getColumnIndex(Phone.NUMBER));
         int typeConstant = cursor.getInt(cursor.getColumnIndex(Phone.TYPE));
@@ -744,17 +740,17 @@ public class ContactService implements G
         if (!found) {
             JSONObject phone = new JSONObject();
             JSONArray types = new JSONArray();
             phone.put("value", value);
             phone.put("type", type);
             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);
         }
     }
 
     private void getAddressDataAsJSONObject(Cursor cursor, JSONArray addresses) throws JSONException {
         String streetAddress = cursor.getString(cursor.getColumnIndex(StructuredPostal.STREET));
         String locality = cursor.getString(cursor.getColumnIndex(StructuredPostal.CITY));
@@ -797,17 +793,17 @@ public class ContactService implements G
             JSONArray types = new JSONArray();
             address.put("streetAddress", streetAddress);
             address.put("locality", locality);
             address.put("region", region);
             address.put("countryName", countryName);
             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);
         }
     }
 
     private void getOrganizationDataAsJSONObject(Cursor cursor, JSONArray organizations,
                                                  JSONArray jobTitles) throws JSONException {
         int organizationColumnIndex = cursor.getColumnIndex(Organization.COMPANY);
@@ -967,17 +963,17 @@ public class ContactService implements G
             } else {
                 insertContact(contactProperties, requestID);
             }
         } catch (JSONException e) {
             throw new IllegalArgumentException(e);
         }
     }
 
-    private void insertContact(final JSONObject contactProperties, final String requestID) throws JSONException {
+    private void insertContact(final JSONObject contactProperties, final String requestID) {
         ArrayList<ContentProviderOperation> newContactOptions = new ArrayList<ContentProviderOperation>();
 
         // Account to save the contact under
         newContactOptions.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
                          .withValue(RawContacts.ACCOUNT_NAME, mAccountName)
                          .withValue(RawContacts.ACCOUNT_TYPE, mAccountType)
                          .build());
 
@@ -1013,17 +1009,17 @@ public class ContactService implements G
 
         Log.i(LOGTAG, "Sending return status: " + returnStatus);
 
         sendCallbackToJavascript("Android:Contact:Save:Return:" + returnStatus, requestID,
                                  new String[] {"contactID", "reason"},
                                  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
         // delete the contact and re-insert it? Because that would change the contact ID and the
         // Mozilla contacts API shouldn't have this behavior. The solution is to delete each
         // row from the contacts data table that belongs to the contact, and insert the new
         // fields. But then why not just delete all the data from the data in one go and
         // insert the new data in another? Because if all the data relating to a contact is
@@ -1064,71 +1060,75 @@ public class ContactService implements G
         // Update the contact!
         applyBatch(updateContactOptions);
 
         sendCallbackToJavascript("Android:Contact:Save:Return:OK", requestID,
                                  new String[] {"contactID", "reason"},
                                  new Object[] {rawContactId, "update"});
     }
 
-    private List<ContentValues> getContactValues(final JSONObject contactProperties) throws JSONException {
+    private List<ContentValues> getContactValues(final JSONObject contactProperties) {
         List<ContentValues> contactValues = new ArrayList<ContentValues>();
 
-        // 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);
+
+            getEventValues(contactProperties.optString("bday"), Event.TYPE_BIRTHDAY, contactValues);
 
-        getCategoriesValues(contactProperties.optJSONArray("category"), contactValues);
+            getEventValues(contactProperties.optString("anniversary"), Event.TYPE_ANNIVERSARY, contactValues);
 
-        getEventValues(contactProperties.optString("bday"), Event.TYPE_BIRTHDAY, contactValues);
+            getCustomMimetypeValues(contactProperties.optString("sex"), MIMETYPE_SEX, contactValues);
 
-        getEventValues(contactProperties.optString("anniversary"), Event.TYPE_ANNIVERSARY, 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;
     }
 
     private void getGenericValues(final String mimeType, final String dataType, final JSONArray fields,
                                   List<ContentValues> newContactValues) throws JSONException {
         if (fields == null) {
             return;
@@ -1234,17 +1234,17 @@ public class ContactService implements G
 
             // If a custom type, add a label
             if (typeConstant == BaseTypes.TYPE_CUSTOM) {
                 contentValues.put(StructuredPostal.LABEL, type);
             }
         }
 
         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;
     }
 
     private void getPhonesValues(final JSONArray phones, List<ContentValues> newContactValues) throws JSONException {
         if (phones == null) {
             return;
@@ -1258,25 +1258,25 @@ public class ContactService implements G
             if (phoneTypes != null && phoneTypes.length() > 0) {
                 for (int j = 0; j < phoneTypes.length(); j++) {
                     // Translate the phone type string to an integer constant
                     // provided by the ContactsContract API
                     final String type = phoneTypes.getString(j);
                     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"));
                     }
                     newContactValues.add(contentValues);
                 }
             } 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"));
                 }
                 newContactValues.add(contentValues);
             }
         }
     }
 
@@ -1294,22 +1294,22 @@ public class ContactService implements G
                     // Translate the email type string to an integer constant
                     // provided by the ContactsContract API
                     final String type = emailTypes.getString(j);
                     final int typeConstant = getEmailType(type);
 
                     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")));
             }
         }
     }
 
     private void getPhotosValues(final JSONArray photos, List<ContentValues> newContactValues) throws JSONException {
         if (photos == null) {
             return;
         }
@@ -1344,22 +1344,22 @@ public class ContactService implements G
                     // Translate the website type string to an integer constant
                     // provided by the ContactsContract API
                     final String type = websiteTypes.getString(j);
                     final int typeConstant = getWebsiteType(type);
 
                     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")));
             }
         }
     }
 
     private void getImsValues(final JSONArray ims, List<ContentValues> newContactValues) throws JSONException {
         if (ims == null) {
             return;
         }
@@ -1373,22 +1373,22 @@ public class ContactService implements G
                     // Translate the IM type string to an integer constant
                     // provided by the ContactsContract API
                     final String type = imTypes.getString(j);
                     final int typeConstant = getImType(type);
 
                     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")));
             }
         }
     }
 
     private void getCategoriesValues(final JSONArray categories, List<ContentValues> newContactValues) throws JSONException {
         if (categories == null) {
             return;
         }
@@ -1458,21 +1458,21 @@ public class ContactService implements G
             mozillaContactsFlag.put("1");
             getGenericValues(MIMETYPE_MOZILLA_CONTACTS_FLAG, CUSTOM_DATA_COLUMN, mozillaContactsFlag, newContactValues);
         } catch (JSONException e) {
             throw new IllegalArgumentException(e);
         }
     }
 
     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);
 
             // If a custom type, add a label
             if (typeConstant == BaseTypes.TYPE_CUSTOM) {
                 contentValues.put(Data.DATA3, type);
             }
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -130,16 +130,17 @@
 @BINPATH@/components/directory.xpt
 @BINPATH@/components/docshell.xpt
 @BINPATH@/components/dom.xpt
 @BINPATH@/components/dom_apps.xpt
 @BINPATH@/components/dom_base.xpt
 @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
 @BINPATH@/components/dom_file.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
--- a/mobile/android/modules/ContactService.jsm
+++ b/mobile/android/modules/ContactService.jsm
@@ -59,25 +59,17 @@ let ContactService = {
     delete this._requestMessages[aRequestID];
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (DEBUG) {
       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
     let returnMessageTopic = aTopic.substring(8);
 
     switch (aTopic) {
       case "Android:Contacts:Find:Return:OK":
         this._sendAndDeleteReturnMessage(returnMessageTopic, requestID, {requestID: requestID, contacts: message.contacts});