Bug 946294 - Use cached sequences for array attributes in the Contacts interfaces. r=bz r=gwagner

This commit is contained in:
Reuben Morais 2013-12-29 15:41:35 -02:00
parent 1c66dd5712
commit 735e24afed
4 changed files with 105 additions and 470 deletions

View File

@ -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: {}};
try {
for (let field of PROPERTIES) {
if (aContact[field]) {
// 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
]);

View File

@ -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}

View File

@ -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();

View File

@ -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<DOMString>? 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<DOMString>? 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<DOMString>? type,
optional DOMString? value,
optional boolean? pref);
object toJSON();
};
dictionary ContactFieldInit {
dictionary ContactField {
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);
object toJSON();
};
dictionary ContactTelFieldInit : ContactFieldInit {
dictionary ContactTelField : ContactField {
DOMString? carrier;
};
dictionary ContactProperties {
Date? bday;
Date? anniversary;
@ -85,13 +33,13 @@ dictionary ContactProperties {
sequence<Blob>? photo;
sequence<ContactAddressInit>? adr;
sequence<ContactAddress>? adr;
sequence<ContactFieldInit>? email;
sequence<ContactFieldInit>? url;
sequence<ContactFieldInit>? impp;
sequence<ContactField>? email;
sequence<ContactField>? url;
sequence<ContactField>? impp;
sequence<ContactTelFieldInit>? tel;
sequence<ContactTelField>? tel;
sequence<DOMString>? name;
sequence<DOMString>? honorificPrefix;
@ -120,33 +68,33 @@ interface mozContact {
attribute DOMString? sex;
attribute DOMString? genderIdentity;
attribute object? photo;
[Cached, Pure] attribute sequence<Blob>? photo;
attribute object? adr;
[Cached, Pure] attribute sequence<ContactAddress>? adr;
attribute object? email;
attribute object? url;
attribute object? impp;
[Cached, Pure] attribute sequence<ContactField>? email;
[Cached, Pure] attribute sequence<ContactField>? url;
[Cached, Pure] attribute sequence<ContactField>? impp;
attribute object? tel;
[Cached, Pure] attribute sequence<ContactTelField>? 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<DOMString>? name;
[Cached, Pure] attribute sequence<DOMString>? honorificPrefix;
[Cached, Pure] attribute sequence<DOMString>? givenName;
[Cached, Pure] attribute sequence<DOMString>? additionalName;
[Cached, Pure] attribute sequence<DOMString>? familyName;
[Cached, Pure] attribute sequence<DOMString>? honorificSuffix;
[Cached, Pure] attribute sequence<DOMString>? nickname;
[Cached, Pure] attribute sequence<DOMString>? category;
[Cached, Pure] attribute sequence<DOMString>? org;
[Cached, Pure] attribute sequence<DOMString>? jobTitle;
[Cached, Pure] attribute sequence<DOMString>? note;
[Cached, Pure] attribute sequence<DOMString>? key;
[ChromeOnly]
void setMetadata(DOMString id, Date? published, Date? updated);
object toJSON();
jsonifier;
};
dictionary ContactFindSortOptions {