From 62b01698c2b0256f04feeeb646df9420c81bc649 Mon Sep 17 00:00:00 2001 From: Reuben Morais Date: Sun, 29 Dec 2013 15:41:35 -0200 Subject: [PATCH] Bug 946294 - Use cached sequences for array attributes in the Contacts interfaces. r=bz r=gwagner --- dom/contacts/ContactManager.js | 381 +----------------- dom/contacts/ContactManager.manifest | 9 - dom/contacts/tests/test_contacts_basics2.html | 65 ++- dom/webidl/Contacts.webidl | 120 ++---- 4 files changed, 105 insertions(+), 470 deletions(-) diff --git a/dom/contacts/ContactManager.js b/dom/contacts/ContactManager.js index 81232a3a6d97..683b1f9c3886 100644 --- a/dom/contacts/ContactManager.js +++ b/dom/contacts/ContactManager.js @@ -19,288 +19,28 @@ XPCOMUtils.defineLazyServiceGetter(Services, "DOMRequest", "@mozilla.org/dom/dom-request-service;1", "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() { } - -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; - }, - - 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; - }, - - classID: Components.ID("{9cbfa81c-bcab-4ca9-b0d2-f4318f295e33}"), - contractID: "@mozilla.org/contactAddress;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), -}; - -function ContactFieldImpl() { } - -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; - }, - - 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; - }, - - classID: Components.ID("{ad19a543-69e4-44f0-adfa-37c011556bc1}"), - contractID: "@mozilla.org/contactField;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), -}; - -function ContactTelFieldImpl() { } - -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; - }, - - 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; - }, - - classID: Components.ID("{4d42c5a9-ea5d-4102-80c3-40cc986367ca}"), - contractID: "@mozilla.org/contactTelField;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), -}; - -function validateArrayField(data, createCb) { - // We use an array-like Proxy to validate data set by content, since we don't - // have WebIDL arrays yet. See bug 851726. - - // ArrayPropertyExposedPropsProxy is used to return "rw" for any valid index - // and "length" in __exposedProps__. - const ArrayPropertyExposedPropsProxy = new Proxy({}, { - get: function(target, name) { - // Test for index access - if (String(name >>> 0) === name) { - return "rw"; - } - if (name === "length") { - return "r"; - } - } - }); - - const ArrayPropertyHandler = { - set: function(target, name, val, receiver) { - // Test for index access - if (String(name >>> 0) === name) { - target[name] = createCb(val); - } - }, - get: function(target, name) { - if (name === "__exposedProps__") { - return ArrayPropertyExposedPropsProxy; - } - return target[name]; - } - }; - - if (data === null || data === undefined) { - return undefined; - } - - data = Array.isArray(data) ? data : [data]; - let filtered = []; - for (let i = 0, n = data.length; i < n; ++i) { - if (data[i]) { - filtered.push(createCb(data[i])); - } - } - return new Proxy(filtered, ArrayPropertyHandler); -} - // 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" + "bday", "note", "anniversary", "sex", "genderIdentity", "key", "adr", "email", + "url", "impp", "tel" ]; -const ADDRESS_PROPERTIES = ["adr"]; -const FIELD_PROPERTIES = ["email", "url", "impp"]; -const TELFIELD_PROPERTIES = ["tel"]; 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. - set email(aEmail) { - this._email = aEmail; - }, - - 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; - }, - - 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; - }, - - 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; - }, - - 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; - }, - - 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; - }, - __init: function(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; - this.photo = aProp.photo; - this.url = aProp.url; - this.category = aProp.category; - this.adr = aProp.adr; - this.tel = aProp.tel; - this.org = aProp.org; - this.jobTitle = aProp.jobTitle; - this.bday = aProp.bday; - this.note = aProp.note; - this.impp = aProp.impp; - this.anniversary = aProp.anniversary; - this.sex = aProp.sex; - this.genderIdentity = aProp.genderIdentity; - this.key = aProp.key; + for (let prop in aProp) { + this[prop] = aProp[prop]; + } }, setMetadata: function(aId, aPublished, aUpdated) { @@ -313,69 +53,9 @@ Contact.prototype = { } }, - toJSON: function() { - return { - id: this.id, - published: this.published, - updated: this.updated, - - name: this.name, - honorificPrefix: this.honorificPrefix, - givenName: this.givenName, - additionalName: this.additionalName, - familyName: this.familyName, - honorificSuffix: this.honorificSuffix, - nickname: this.nickname, - category: this.category, - org: this.org, - jobTitle: this.jobTitle, - note: this.note, - sex: this.sex, - genderIdentity: this.genderIdentity, - email: this.email, - photo: this.photo, - adr: this.adr, - url: this.url, - tel: this.tel, - bday: this.bday, - impp: this.impp, - anniversary: this.anniversary, - key: this.key, - - __exposedProps__: { - id: "rw", - published: "rw", - updated: "rw", - name: "rw", - honorificPrefix: "rw", - givenName: "rw", - additionalName: "rw", - familyName: "rw", - honorificSuffix: "rw", - nickname: "rw", - category: "rw", - org: "rw", - jobTitle: "rw", - note: "rw", - sex: "rw", - genderIdentity: "rw", - email: "rw", - photo: "rw", - adr: "rw", - url: "rw", - tel: "rw", - bday: "rw", - impp: "rw", - anniversary: "rw", - key: "rw", - } - }; - }, - classID: Components.ID("{72a5ee28-81d8-4af8-90b3-ae935396cc66}"), contractID: "@mozilla.org/contact;1", - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, - Ci.nsIDOMGlobalPropertyInitializer]), + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), }; function ContactManager() { } @@ -599,50 +279,19 @@ ContactManager.prototype = { // mozContact object. let newContact = {properties: {}}; - for (let field of PROPERTIES) { - if (aContact[field]) { + try { + for (let field of PROPERTIES) { + // This hack makes sure modifications to the sequence attributes get validated. + aContact[field] = 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); - } - } - } - } - - 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); - } - } - } + } catch (e) { + // And then make sure we throw a proper error message (no internal file and line #) + throw new this._window.DOMError(e.name, e.message); } let request = this.createRequest(); - let requestID = this.getRequestId({request: request, reason: reason}); + let requestID = this.getRequestId({request: request}); let reason; if (aContact.id == "undefined") { @@ -816,5 +465,5 @@ ContactManager.prototype = { }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ - Contact, ContactManager, ContactFieldImpl, ContactAddressImpl, ContactTelFieldImpl + Contact, ContactManager ]); diff --git a/dom/contacts/ContactManager.manifest b/dom/contacts/ContactManager.manifest index a0938fe90eba..b2dbe07619c2 100644 --- a/dom/contacts/ContactManager.manifest +++ b/dom/contacts/ContactManager.manifest @@ -1,12 +1,3 @@ -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 {72a5ee28-81d8-4af8-90b3-ae935396cc66} ContactManager.js contract @mozilla.org/contact;1 {72a5ee28-81d8-4af8-90b3-ae935396cc66} diff --git a/dom/contacts/tests/test_contacts_basics2.html b/dom/contacts/tests/test_contacts_basics2.html index 539b5859d6e8..a7c2027f3974 100644 --- a/dom/contacts/tests/test_contacts_basics2.html +++ b/dom/contacts/tests/test_contacts_basics2.html @@ -552,7 +552,8 @@ var steps = [ honorificPrefix: [], honorificSuffix: [{foo: "bar"}], sex: 17, - genderIdentity: 18 + genderIdentity: 18, + email: [{type: ["foo"], value: "bar"}] }; obj.honorificPrefix.__defineGetter__('0',(function() { var c = 0; @@ -566,13 +567,15 @@ var steps = [ } })()); createResult1 = new mozContact(obj); + createResult1.email.push({aeiou: "abcde"}); req = mozContacts.save(createResult1); req.onsuccess = function () { checkContacts(createResult1, { honorificPrefix: ["string"], honorificSuffix: ["[object Object]"], sex: "17", - genderIdentity: "18" + genderIdentity: "18", + email: [{type: ["foo"], value: "bar"}, {}] }); next(); }; @@ -696,13 +699,13 @@ var steps = [ impp: [{value: undefined}], tel: [{value: undefined}], }); - ise(c.adr[0].streetAddress, null, "adr.streetAddress is null"); - ise(c.adr[0].locality, null, "adr.locality is null"); - ise(c.adr[0].pref, null, "adr.pref is null"); - ise(c.email[0].value, null, "email.value is null"); - ise(c.url[0].value, null, "url.value is null"); - ise(c.impp[0].value, null, "impp.value is null"); - ise(c.tel[0].value, null, "tel.value is null"); + ise(c.adr[0].streetAddress, undefined, "adr.streetAddress is undefined"); + ise(c.adr[0].locality, undefined, "adr.locality is undefined"); + ise(c.adr[0].pref, undefined, "adr.pref is undefined"); + ise(c.email[0].value, undefined, "email.value is undefined"); + ise(c.url[0].value, undefined, "url.value is undefined"); + ise(c.impp[0].value, undefined, "impp.value is undefined"); + ise(c.tel[0].value, undefined, "tel.value is undefined"); next(); }, function() { @@ -721,6 +724,50 @@ var steps = [ testArrayProp("url"); next(); }, + function() { + ok(true, "Passing a mozContact with invalid data to save() should throw"); + var c = new mozContact({ + photo: [], + tel: [] + }); + c.photo.push({}); + SimpleTest.doesThrow(()=>navigator.mozContacts.save(c), "Invalid data in Blob array"); + c.tel.push(123); + SimpleTest.doesThrow(()=>navigator.mozContacts.save(c), "Invalid data in dictionary array"); + next(); + }, + function() { + ok(true, "Inline changes to array properties should be seen by save"); + var c = new mozContact({ + name: [], + familyName: [], + givenName: [], + nickname: [], + tel: [], + adr: [], + email: [] + }); + for (var prop of Object.getOwnPropertyNames(properties1)) { + if (!Array.isArray(properties1[prop])) { + continue; + } + for (var i = 0; i < properties1[prop].length; ++i) { + c[prop].push(properties1[prop][i]); + } + } + req = navigator.mozContacts.save(c); + req.onsuccess = function() { + req = navigator.mozContacts.find(defaultOptions); + req.onsuccess = function() { + ise(req.result.length, 1, "Got 1 contact"); + checkContacts(req.result[0], properties1); + next(); + }; + req.onerror = onFailure; + }; + req.onerror = onFailure; + }, + clearDatabase, function () { ok(true, "all done!\n"); SimpleTest.finish(); diff --git a/dom/webidl/Contacts.webidl b/dom/webidl/Contacts.webidl index a5780bb66882..61377fb41112 100644 --- a/dom/webidl/Contacts.webidl +++ b/dom/webidl/Contacts.webidl @@ -4,29 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ -[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactAddress;1"] -interface ContactAddress { - attribute object? type; // DOMString[] - attribute DOMString? streetAddress; - attribute DOMString? locality; - attribute DOMString? region; - attribute DOMString? postalCode; - attribute DOMString? countryName; - attribute boolean? pref; - - [ChromeOnly] - void initialize(optional sequence? type, - optional DOMString? streetAddress, - optional DOMString? locality, - optional DOMString? region, - optional DOMString? postalCode, - optional DOMString? countryName, - optional boolean? pref); - - object toJSON(); -}; - -dictionary ContactAddressInit { +dictionary ContactAddress { sequence? type; DOMString? streetAddress; DOMString? locality; @@ -36,46 +14,16 @@ dictionary ContactAddressInit { boolean? pref; }; - -[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactField;1"] -interface ContactField { - attribute object? type; // DOMString[] - attribute DOMString? value; - attribute boolean? pref; - - [ChromeOnly] - void initialize(optional sequence? type, - optional DOMString? value, - optional boolean? pref); - - object toJSON(); -}; - -dictionary ContactFieldInit { +dictionary ContactField { sequence? type; DOMString? value; boolean? pref; }; - -[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactTelField;1"] -interface ContactTelField : ContactField { - attribute DOMString? carrier; - - [ChromeOnly] - void initialize(optional sequence? type, - optional DOMString? value, - optional DOMString? carrier, - optional boolean? pref); - - object toJSON(); -}; - -dictionary ContactTelFieldInit : ContactFieldInit { +dictionary ContactTelField : ContactField { DOMString? carrier; }; - dictionary ContactProperties { Date? bday; Date? anniversary; @@ -85,13 +33,13 @@ dictionary ContactProperties { sequence? photo; - sequence? adr; + sequence? adr; - sequence? email; - sequence? url; - sequence? impp; + sequence? email; + sequence? url; + sequence? impp; - sequence? tel; + sequence? tel; sequence? name; sequence? honorificPrefix; @@ -110,43 +58,43 @@ dictionary ContactProperties { [Constructor(optional ContactProperties properties), JSImplementation="@mozilla.org/contact;1"] interface mozContact { - attribute DOMString id; - readonly attribute Date? published; - readonly attribute Date? updated; + attribute DOMString id; + readonly attribute Date? published; + readonly attribute Date? updated; - attribute Date? bday; - attribute Date? anniversary; + attribute Date? bday; + attribute Date? anniversary; - attribute DOMString? sex; - attribute DOMString? genderIdentity; + attribute DOMString? sex; + attribute DOMString? genderIdentity; - attribute object? photo; + [Cached, Pure] attribute sequence? photo; - attribute object? adr; + [Cached, Pure] attribute sequence? adr; - attribute object? email; - attribute object? url; - attribute object? impp; + [Cached, Pure] attribute sequence? email; + [Cached, Pure] attribute sequence? url; + [Cached, Pure] attribute sequence? impp; - attribute object? tel; + [Cached, Pure] attribute sequence? 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; + [Cached, Pure] attribute sequence? name; + [Cached, Pure] attribute sequence? honorificPrefix; + [Cached, Pure] attribute sequence? givenName; + [Cached, Pure] attribute sequence? additionalName; + [Cached, Pure] attribute sequence? familyName; + [Cached, Pure] attribute sequence? honorificSuffix; + [Cached, Pure] attribute sequence? nickname; + [Cached, Pure] attribute sequence? category; + [Cached, Pure] attribute sequence? org; + [Cached, Pure] attribute sequence? jobTitle; + [Cached, Pure] attribute sequence? note; + [Cached, Pure] attribute sequence? key; [ChromeOnly] void setMetadata(DOMString id, Date? published, Date? updated); - object toJSON(); + jsonifier; }; dictionary ContactFindSortOptions {