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 163017 d09f86ec45cde70e2199368bc38d669b715120b8
parent 163016 b85ed93dee3b409e1b69c9f6de0cd98ba57cca1f
child 163018 de0be3ff99cf118eddf4b801470ce0a7b4308623
child 163044 044255192754a5fad12d4b30f9683f0c393c9c7c
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs916267, 850430
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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});