Bug 674720 - WebContacts (or Contacts+). r=fabrice, jonas, bent, tantek

This commit is contained in:
Gregor Wagner 2012-02-28 14:01:48 -08:00
parent 146f1ad4c9
commit 1526b400d5
18 changed files with 1795 additions and 1 deletions

View File

@ -410,6 +410,10 @@ pref("dom.mozBrowserFramesWhitelist", "http://localhost:7777");
pref("dom.sms.enabled", true);
pref("dom.sms.whitelist", "file://,http://localhost:7777");
// Temporary permission hack for WebContacts
pref("dom.mozContacts.enabled", true);
pref("dom.mozContacts.whitelist", "http://localhost:7777");
// Ignore X-Frame-Options headers.
pref("b2g.ignoreXFrameOptions", true);

View File

@ -16,6 +16,7 @@ const LocalFile = CC('@mozilla.org/file/local;1',
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/ContactService.jsm');
XPCOMUtils.defineLazyGetter(Services, 'env', function() {
return Cc['@mozilla.org/process/environment;1']
@ -60,7 +61,7 @@ function startupHttpd(baseDir, port) {
// XXX never grant 'content-camera' to non-gaia apps
function addPermissions(urls) {
let permissions = [
'indexedDB', 'indexedDB-unlimited', 'webapps-manage', 'offline-app', 'content-camera'
'indexedDB', 'indexedDB-unlimited', 'webapps-manage', 'offline-app', 'content-camera', 'webcontacts-manage'
];
urls.forEach(function(url) {
let uri = Services.io.newURI(url, null, null);

View File

@ -155,6 +155,7 @@
@BINPATH@/components/dom_bluetooth.xpt
#endif
@BINPATH@/components/dom_canvas.xpt
@BINPATH@/components/dom_contacts.xpt
@BINPATH@/components/dom_core.xpt
@BINPATH@/components/dom_css.xpt
@BINPATH@/components/dom_events.xpt
@ -294,6 +295,8 @@
; JavaScript components
@BINPATH@/components/ConsoleAPI.manifest
@BINPATH@/components/ConsoleAPI.js
@BINPATH@/components/ContactManager.js
@BINPATH@/components/ContactManager.manifest
@BINPATH@/components/FeedProcessor.manifest
@BINPATH@/components/FeedProcessor.js
@BINPATH@/components/BrowserFeeds.manifest

View File

@ -155,6 +155,7 @@
@BINPATH@/components/dom_bluetooth.xpt
#endif
@BINPATH@/components/dom_canvas.xpt
@BINPATH@/components/dom_contacts.xpt
@BINPATH@/components/dom_core.xpt
@BINPATH@/components/dom_css.xpt
@BINPATH@/components/dom_events.xpt
@ -409,6 +410,9 @@
@BINPATH@/components/messageWakeupService.js
@BINPATH@/components/messageWakeupService.manifest
@BINPATH@/components/ContactManager.js
@BINPATH@/components/ContactManager.manifest
; Modules
@BINPATH@/modules/*

View File

@ -47,6 +47,7 @@ MODULE = dom
DIRS = \
interfaces/base \
interfaces/canvas \
interfaces/contacts \
interfaces/core \
interfaces/html \
interfaces/events \
@ -77,6 +78,7 @@ endif
DIRS += \
base \
battery \
contacts \
power \
sms \
src \

View File

@ -0,0 +1,404 @@
/* 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/. */
"use strict"
/* static functions */
let DEBUG = 0;
if (DEBUG)
debug = function (s) { dump("-*- ContactManager: " + s + "\n"); }
else
debug = function (s) {}
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");
const nsIClassInfo = Ci.nsIClassInfo;
const CONTACTPROPERTIES_CID = Components.ID("{53ed7c20-ceda-11e0-9572-0800200c9a66}");
const nsIDOMContactProperties = Ci.nsIDOMContactProperties;
// ContactProperties is not directly instantiated. It is used as interface.
ContactProperties.prototype = {
classID : CONTACTPROPERTIES_CID,
classInfo : XPCOMUtils.generateCI({classID: CONTACTPROPERTIES_CID,
contractID:"@mozilla.org/contactProperties;1",
classDescription: "ContactProperties",
interfaces: [nsIDOMContactProperties],
flags: nsIClassInfo.DOM_OBJECT}),
QueryInterface : XPCOMUtils.generateQI([nsIDOMContactProperties])
}
//ContactAddress
const CONTACTADDRESS_CONTRACTID = "@mozilla.org/contactAddress;1";
const CONTACTADDRESS_CID = Components.ID("{27a568b0-cee1-11e0-9572-0800200c9a66}");
const nsIDOMContactAddress = Components.interfaces.nsIDOMContactAddress;
function ContactAddress(aStreetAddress, aLocality, aRegion, aPostalCode, aCountryName) {
this.streetAddress = aStreetAddress || null;
this.locality = aLocality || null;
this.region = aRegion || null;
this.postalCode = aPostalCode || null;
this.countryName = aCountryName || null;
};
function ContactProperties(aProp) { debug("ContactProperties Constructor"); }
ContactAddress.prototype = {
classID : CONTACTADDRESS_CID,
classInfo : XPCOMUtils.generateCI({classID: CONTACTADDRESS_CID,
contractID: CONTACTADDRESS_CONTRACTID,
classDescription: "ContactAddress",
interfaces: [nsIDOMContactAddress],
flags: nsIClassInfo.DOM_OBJECT}),
QueryInterface : XPCOMUtils.generateQI([nsIDOMContactAddress])
}
//ContactFindOptions
const CONTACTFINDOPTIONS_CONTRACTID = "@mozilla.org/contactFindOptions;1";
const CONTACTFINDOPTIONS_CID = Components.ID("{e31daea0-0cb6-11e1-be50-0800200c9a66}");
const nsIDOMContactFindOptions = Components.interfaces.nsIDOMContactFindOptions;
function ContactFindOptions(aFilterValue, aFilterBy, aFilterOp, aFilterLimit) {
this.filterValue = aFilterValue || '';
this.filterBy = new Array();
for (let field in aFilterBy)
this.filterBy.push(field);
this.filterOp = aFilterOp || '';
this.filterLimit = aFilterLimit || 0;
};
ContactFindOptions.prototype = {
classID : CONTACTFINDOPTIONS_CID,
classInfo : XPCOMUtils.generateCI({classID: CONTACTFINDOPTIONS_CID,
contractID: CONTACTFINDOPTIONS_CONTRACTID,
classDescription: "ContactFindOptions",
interfaces: [nsIDOMContactFindOptions],
flags: nsIClassInfo.DOM_OBJECT}),
QueryInterface : XPCOMUtils.generateQI([nsIDOMContactFindOptions])
}
//Contact
const CONTACT_CONTRACTID = "@mozilla.org/contact;1";
const CONTACT_CID = Components.ID("{da0f7040-388b-11e1-b86c-0800200c9a66}");
const nsIDOMContact = Components.interfaces.nsIDOMContact;
function Contact() { debug("Contact constr: "); };
Contact.prototype = {
init: function init(aProp) {
// Accept non-array strings for DOMString[] properties and convert them.
function _create(aField) {
if (typeof aField == "string")
return new Array(aField);
return aField;
};
this.name = _create(aProp.name) || null;
this.honorificPrefix = _create(aProp.honorificPrefix) || null;
this.givenName = _create(aProp.givenName) || null;
this.additionalName = _create(aProp.additionalName) || null;
this.familyName = _create(aProp.familyName) || null;
this.honorificSuffix = _create(aProp.honorificSuffix) || null;
this.nickname = _create(aProp.nickname) || null;
this.email = _create(aProp.email) || null;
this.photo = _create(aProp.photo) || null;
this.url = _create(aProp.url) || null;
this.category = _create(aProp.category) || null;
if (aProp.adr) {
// Make sure adr argument is an array. Instanceof doesn't work.
aProp.adr = aProp.adr.length == undefined ? [aProp.adr] : aProp.adr;
this.adr = new Array();
for (let i = 0; i < aProp.adr.length; i++)
this.adr.push(new ContactAddress(aProp.adr[i].streetAddress, aProp.adr[i].locality,
aProp.adr[i].region, aProp.adr[i].postalCode,
aProp.adr[i].countryName));
} else {
this.adr = null;
}
this.tel = _create(aProp.tel) || null;
this.org = _create(aProp.org) || null;
this.bday = (aProp.bday == "undefined" || aProp.bday == null) ? null : new Date(aProp.bday);
this.note = _create(aProp.note) || null;
this.impp = _create(aProp.impp) || null;
this.anniversary = (aProp.anniversary == "undefined" || aProp.anniversary == null) ? null : new Date(aProp.anniversary);
this.sex = (aProp.sex != "undefined") ? aProp.sex : null;
this.genderIdentity = (aProp.genderIdentity != "undefined") ? aProp.genderIdentity : null;
},
get published () {
return this._published;
},
set published(aPublished) {
this._published = aPublished;
},
get updated () {
return this._updated;
},
set updated(aUpdated) {
this._updated = aUpdated;
},
classID : CONTACT_CID,
classInfo : XPCOMUtils.generateCI({classID: CONTACT_CID,
contractID: CONTACT_CONTRACTID,
classDescription: "Contact",
interfaces: [nsIDOMContact, nsIDOMContactProperties],
flags: nsIClassInfo.DOM_OBJECT}),
QueryInterface : XPCOMUtils.generateQI([nsIDOMContact, nsIDOMContactProperties])
}
// ContactManager
const CONTACTMANAGER_CONTRACTID = "@mozilla.org/contactManager;1";
const CONTACTMANAGER_CID = Components.ID("{50a820b0-ced0-11e0-9572-0800200c9a66}");
const nsIDOMContactManager = Components.interfaces.nsIDOMContactManager;
function ContactManager()
{
debug("Constructor");
}
ContactManager.prototype = {
save: function save(aContact) {
let request;
if (this.hasPrivileges) {
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: [],
bday: null,
note: [],
impp: [],
anniversary: null,
sex: null,
genderIdentity: null
};
for (let field in newContact.properties)
newContact.properties[field] = aContact[field];
if (aContact.id == "undefined") {
debug("Create id!");
aContact.id = this._getRandomId();
}
this._setMetaData(newContact, aContact);
debug("send: " + JSON.stringify(newContact));
request = this._rs.createRequest(this._window);
this._mm.sendAsyncMessage("Contact:Save", {contact: newContact,
requestID: this.getRequestId({ request: request })});
return request;
} else {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
},
remove: function removeContact(aRecord) {
let request;
if (this.hasPrivileges) {
request = this._rs.createRequest(this._window);
this._mm.sendAsyncMessage("Contact:Remove", {id: aRecord.id,
requestID: this.getRequestId({ request: request })});
return request;
} else {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
},
_setMetaData: function(aNewContact, aRecord) {
aNewContact.id = aRecord.id;
aNewContact.published = aRecord.published;
aNewContact.updated = aRecord.updated;
},
_convertContactsArray: function(aContacts) {
let contacts = new Array();
for (let i in aContacts) {
let newContact = new Contact();
newContact.init(aContacts[i].properties);
this._setMetaData(newContact, aContacts[i]);
contacts.push(newContact);
}
return contacts;
},
getRequestId: function(aRequest) {
let id = "id" + this._getRandomId();
this._requests[id] = aRequest;
return id;
},
getRequest: function(aId) {
if (this._requests[aId])
return this._requests[aId].request;
},
removeRequest: function(aId) {
if (this._requests[aId])
delete this._requests[aId];
},
_getRandomId: function() {
return Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
},
receiveMessage: function(aMessage) {
debug("Contactmanager::receiveMessage: " + aMessage.name);
let msg = aMessage.json;
let contacts = msg.contacts;
switch (aMessage.name) {
case "Contacts:Find:Return:OK":
let req = this.getRequest(msg.requestID);
if (req) {
let result = this._convertContactsArray(contacts);
debug("result: " + JSON.stringify(result));
this._rs.fireSuccess(req, result);
} else {
debug("no request stored!" + msg.requestID);
}
break;
case "Contact:Save:Return:OK":
case "Contacts:Clear:Return:OK":
case "Contact:Remove:Return:OK":
req = this.getRequest(msg.requestID);
if (req)
this._rs.fireSuccess(req, 0);
break;
case "Contacts:Find:Return:KO":
case "Contact:Save:Return:KO":
case "Contact:Remove:Return:KO":
case "Contacts:Clear:Return:KO":
req = this.getRequest(msg.requestID);
if (req)
this._rs.fireError(req, msg.errorMsg);
break;
default:
debug("Wrong message: " + aMessage.name);
}
this.removeRequest(msg.requestID);
},
find: function(aOptions) {
let request;
if (this.hasPrivileges) {
request = this._rs.createRequest(this._window);
this._mm.sendAsyncMessage("Contacts:Find", {findOptions: aOptions,
requestID: this.getRequestId({ request: request })});
return request;
} else {
debug("find not allowed");
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
},
clear: function() {
let request;
if (this.hasPrivileges) {
request = this._rs.createRequest(this._window);
this._mm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({ request: request })});
return request;
} else {
debug("clear not allowed");
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
},
init: function(aWindow) {
// Set navigator.mozContacts to null.
if (!Services.prefs.getBoolPref("dom.mozContacts.enabled"))
return null;
this._window = aWindow;
this._messages = ["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"];
this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
this._messages.forEach((function(msgName) {
this._mm.addMessageListener(msgName, this);
}).bind(this));
this._rs = Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService);
this._requests = [];
Services.obs.addObserver(this, "inner-window-destroyed", false);
let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
this._innerWindowID = util.currentInnerWindowID;
let principal = aWindow.document.nodePrincipal;
let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
let perm = principal == secMan.getSystemPrincipal() ?
Ci.nsIPermissionManager.ALLOW_ACTION :
Services.perms.testExactPermission(principal.URI, "webcontacts-manage");
//only pages with perm set can use the contacts
this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
debug("has privileges :" + this.hasPrivileges);
},
observe: function(aSubject, aTopic, aData) {
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (wId == this.innerWindowID) {
Services.obs.removeObserver(this, "inner-window-destroyed");
this._messages.forEach((function(msgName) {
this._mm.removeMessageListener(msgName, this);
}).bind(this));
this._mm = null;
this._messages = null;
this._requests = null;
this._window = null;
this._innerWindowID = null;
}
},
classID : CONTACTMANAGER_CID,
QueryInterface : XPCOMUtils.generateQI([nsIDOMContactManager, Ci.nsIDOMGlobalPropertyInitializer]),
classInfo : XPCOMUtils.generateCI({classID: CONTACTMANAGER_CID,
contractID: CONTACTMANAGER_CONTRACTID,
classDescription: "ContactManager",
interfaces: [nsIDOMContactManager],
flags: nsIClassInfo.DOM_OBJECT})
}
const NSGetFactory = XPCOMUtils.generateNSGetFactory([Contact, ContactManager, ContactProperties, ContactAddress, ContactFindOptions])

View File

@ -0,0 +1,16 @@
component {53ed7c20-ceda-11e0-9572-0800200c9a66} ContactManager.js
contract @mozilla.org/contactProperties;1 {53ed7c20-ceda-11e0-9572-0800200c9a66}
component {27a568b0-cee1-11e0-9572-0800200c9a66} ContactManager.js
contract @mozilla.org/contactAddress;1 {27a568b0-cee1-11e0-9572-0800200c9a66}
component {e31daea0-0cb6-11e1-be50-0800200c9a66} ContactManager.js
contract @mozilla.org/contactFindOptions;1 {e31daea0-0cb6-11e1-be50-0800200c9a66}
component {da0f7040-388b-11e1-b86c-0800200c9a66} ContactManager.js
contract @mozilla.org/contact;1 {da0f7040-388b-11e1-b86c-0800200c9a66}
category JavaScript-global-constructor mozContact @mozilla.org/contact;1
component {50a820b0-ced0-11e0-9572-0800200c9a66} ContactManager.js
contract @mozilla.org/contactManager;1 {50a820b0-ced0-11e0-9572-0800200c9a66}
category JavaScript-navigator-property mozContacts @mozilla.org/contactManager;1

47
dom/contacts/Makefile.in Normal file
View File

@ -0,0 +1,47 @@
# 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/.
DEPTH = ../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = \
$(srcdir) \
$(NULL)
include $(DEPTH)/config/autoconf.mk
ifeq ($(MOZ_WIDGET_TOOLKIT),gonk)
VPATH += $(srcdir)/fallback
endif
MODULE = dom
LIBRARY_NAME = jsdomcontacts_s
LIBXUL_LIBRARY = 1
EXTRA_COMPONENTS = \
ContactManager.js \
ContactManager.manifest \
$(NULL)
ifeq ($(MOZ_WIDGET_TOOLKIT),gonk)
EXTRA_JS_MODULES = ContactService.jsm \
$(NULL)
EXTRA_JS_MODULES += ContactDB.jsm \
$(NULL)
endif
ifdef ENABLE_TESTS
DIRS += tests
endif
# Add VPATH to LOCAL_INCLUDES so we are going to include the correct backend
# subdirectory (and the ipc one).
LOCAL_INCLUDES += $(VPATH:%=-I%)
include $(topsrcdir)/config/config.mk
include $(topsrcdir)/ipc/chromium/chromium-config.mk
include $(topsrcdir)/config/rules.mk
DEFINES += -D_IMPL_NS_LAYOUT

View File

@ -0,0 +1,410 @@
/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ['ContactDB'];
let DEBUG = 0;
/* static functions */
if (DEBUG)
debug = function (s) { dump("-*- ContactDB component: " + s + "\n"); }
else
debug = function (s) {}
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
const DB_NAME = "contacts";
const DB_VERSION = 1;
const STORE_NAME = "contacts";
function ContactDB(aGlobal) {
debug("Constructor");
this._indexedDB = aGlobal.mozIndexedDB;
}
ContactDB.prototype = {
// Cache the DB
db: null,
close: function close() {
debug("close");
if (this.db)
this.db.close();
},
/**
* Prepare the database. This may include opening the database and upgrading
* it to the latest schema version.
*
* @return (via callback) a database ready for use.
*/
ensureDB: function ensureDB(callback, failureCb) {
if (this.db) {
debug("ensureDB: already have a database, returning early.");
callback(this.db);
return;
}
let self = this;
debug("try to open database:" + DB_NAME + " " + DB_VERSION);
let request = this._indexedDB.open(DB_NAME, DB_VERSION);
request.onsuccess = function (event) {
debug("Opened database:", DB_NAME, DB_VERSION);
self.db = event.target.result;
self.db.onversionchange = function(event) {
debug("WARNING: DB modified from a different window.");
}
callback(self.db);
};
request.onupgradeneeded = function (event) {
debug("Database needs upgrade:" + DB_NAME + event.oldVersion + event.newVersion);
debug("Correct new database version:" + event.newVersion == DB_VERSION);
let db = event.target.result;
switch (event.oldVersion) {
case 0:
debug("New database");
self.createSchema(db);
break;
default:
debug("No idea what to do with old database version:" + event.oldVersion);
failureCb(event.target.errorMessage);
break;
}
};
request.onerror = function (event) {
debug("Failed to open database:", DB_NAME);
failureCb(event.target.errorMessage);
};
request.onblocked = function (event) {
debug("Opening database request is blocked.");
};
},
/**
* Create the initial database schema.
*
* The schema of records stored is as follows:
*
* {id: "...", // UUID
* published: Date(...), // First published date.
* updated: Date(...), // Last updated date.
* properties: {...} // Object holding the ContactProperties
* }
*/
createSchema: function createSchema(db) {
let objectStore = db.createObjectStore(STORE_NAME, {keyPath: "id"});
// Metadata indexes
objectStore.createIndex("published", "published", { unique: false });
objectStore.createIndex("updated", "updated", { unique: false });
// Properties indexes
objectStore.createIndex("nickname", "properties.nickname", { unique: false, multiEntry: true });
objectStore.createIndex("name", "properties.name", { unique: false, multiEntry: true });
objectStore.createIndex("familyName", "properties.familyName", { unique: false, multiEntry: true });
objectStore.createIndex("givenName", "properties.givenName", { unique: false, multiEntry: true });
objectStore.createIndex("tel", "properties.tel", { unique: false, multiEntry: true });
objectStore.createIndex("email", "properties.email", { unique: false, multiEntry: true });
objectStore.createIndex("note", "properties.note", { unique: false, multiEntry: true });
debug("Created object stores and indexes");
},
/**
* Start a new transaction.
*
* @param txn_type
* Type of transaction (e.g. IDBTransaction.READ_WRITE)
* @param callback
* Function to call when the transaction is available. It will
* be invoked with the transaction and the 'contacts' object store.
* @param successCb [optional]
* Success callback to call on a successful transaction commit.
* @param failureCb [optional]
* Error callback to call when an error is encountered.
*/
newTxn: function newTxn(txn_type, callback, successCb, failureCb) {
this.ensureDB(function (db) {
debug("Starting new transaction" + txn_type);
let txn = db.transaction(STORE_NAME, txn_type);
debug("Retrieving object store", STORE_NAME);
let store = txn.objectStore(STORE_NAME);
txn.oncomplete = function (event) {
debug("Transaction complete. Returning to callback.");
successCb(txn.result);
};
txn.onabort = function (event) {
debug("Caught error on transaction" + event.target.errorCode);
switch(event.target.errorCode) {
case Ci.nsIIDBDatabaseException.ABORT_ERR:
case Ci.nsIIDBDatabaseException.CONSTRAINT_ERR:
case Ci.nsIIDBDatabaseException.DATA_ERR:
case Ci.nsIIDBDatabaseException.TRANSIENT_ERR:
case Ci.nsIIDBDatabaseException.NOT_ALLOWED_ERR:
case Ci.nsIIDBDatabaseException.NOT_FOUND_ERR:
case Ci.nsIIDBDatabaseException.QUOTA_ERR:
case Ci.nsIIDBDatabaseException.READ_ONLY_ERR:
case Ci.nsIIDBDatabaseException.TIMEOUT_ERR:
case Ci.nsIIDBDatabaseException.TRANSACTION_INACTIVE_ERR:
case Ci.nsIIDBDatabaseException.VERSION_ERR:
case Ci.nsIIDBDatabaseException.UNKNOWN_ERR:
failureCb("UnknownError");
break;
default:
debug("Unknown errorCode", event.target.errorCode);
failureCb("UnknownError");
break;
}
};
callback(txn, store);
}, failureCb);
},
// Todo: add searchfields. "Tom" should be a result with T, t, To, to...
makeImport: function makeImport(aContact) {
let contact = {};
contact.properties = {
name: [],
honorificPrefix: [],
givenName: [],
additionalName: [],
familyName: [],
honorificSuffix: [],
nickname: [],
email: [],
photo: [],
url: [],
category: [],
adr: [],
tel: [],
org: [],
bday: null,
note: [],
impp: [],
anniversary: null,
sex: null,
genderIdentity: null
};
for (let field in aContact.properties) {
contact.properties[field] = aContact.properties[field];
}
contact.updated = aContact.updated;
contact.published = aContact.published;
contact.id = aContact.id;
return contact;
},
// Needed to remove searchfields
makeExport: function makeExport(aRecord) {
let contact = {};
contact.properties = aRecord.properties;
for (let field in aRecord.properties)
contact.properties[field] = aRecord.properties[field];
contact.updated = aRecord.updated;
contact.published = aRecord.published;
contact.id = aRecord.id;
return contact;
},
updateRecordMetadata: function updateRecordMetadata(record) {
if (!record.id) {
Cu.reportError("Contact without ID");
}
if (!record.published) {
record.published = new Date();
}
record.updated = new Date();
},
saveContact: function saveContact(aContact, successCb, errorCb) {
let contact = this.makeImport(aContact);
this.newTxn(Ci.nsIIDBTransaction.READ_WRITE, function (txn, store) {
debug("Going to update" + JSON.stringify(contact));
// Look up the existing record and compare the update timestamp.
// If no record exists, just add the new entry.
let newRequest = store.get(contact.id);
newRequest.onsuccess = function (event) {
if (!event.target.result) {
debug("new record!")
this.updateRecordMetadata(contact);
store.put(contact);
} else {
debug("old record!")
if (new Date(typeof contact.updated === "undefined" ? 0 : contact.updated) < new Date(event.target.result.updated)) {
debug("rev check fail!");
txn.abort();
return;
} else {
debug("rev check OK");
contact.published = event.target.result.published;
contact.updated = new Date();
store.put(contact);
}
}
}.bind(this);
}.bind(this), successCb, errorCb);
},
removeContact: function removeContact(aId, aSuccessCb, aErrorCb) {
this.newTxn(Ci.nsIIDBTransaction.READ_WRITE, function (txn, store) {
debug("Going to delete" + aId);
store.delete(aId);
}, aSuccessCb, aErrorCb);
},
clear: function clear(aSuccessCb, aErrorCb) {
this.newTxn(Ci.nsIIDBTransaction.READ_WRITE, function (txn, store) {
debug("Going to clear all!");
store.clear();
}, aSuccessCb, aErrorCb);
},
/**
* @param successCb
* Callback function to invoke with result array.
* @param failureCb [optional]
* Callback function to invoke when there was an error.
* @param options [optional]
* Object specifying search options. Possible attributes:
* - filterBy
* - filterOp
* - filterValue
* - count
* Possibly supported in the future:
* - fields
* - sortBy
* - sortOrder
* - startIndex
*/
find: function find(aSuccessCb, aFailureCb, aOptions) {
debug("ContactDB:find val:" + aOptions.filterValue + " by: " + aOptions.filterBy + " op: " + aOptions.filterOp + "\n");
let self = this;
this.newTxn(Ci.nsIIDBTransaction.READ_ONLY, function (txn, store) {
if (aOptions && aOptions.filterOp == "equals") {
self._findWithIndex(txn, store, aOptions);
} else if (aOptions && aOptions.filterBy) {
self._findWithSearch(txn, store, aOptions);
} else {
self._findAll(txn, store, aOptions);
}
}, aSuccessCb, aFailureCb);
},
_findWithIndex: function _findWithIndex(txn, store, options) {
debug("_findWithIndex: " + options.filterValue +" " + options.filterOp + " " + options.filterBy + " ");
let fields = options.filterBy;
for (let key in fields) {
debug("key: " + fields[key]);
if (!store.indexNames.contains(fields[key]) && !fields[key] == "id") {
debug("Key not valid!" + fields[key] + ", " + store.indexNames);
txn.abort();
return;
}
}
// lookup for all keys
if (options.filterBy.length == 0) {
debug("search in all fields!" + JSON.stringify(store.indexNames));
for(let myIndex = 0; myIndex < store.indexNames.length; myIndex++) {
fields = Array.concat(fields, store.indexNames[myIndex])
}
}
let filter_keys = fields.slice();
for (let key = filter_keys.shift(); key; key = filter_keys.shift()) {
let request;
if (key == "id") {
// store.get would return an object and not an array
request = store.getAll(options.filterValue);
} else {
debug("Getting index: " + key);
let index = store.index(key);
request = index.getAll(options.filterValue, options.filterLimit);
}
if (!txn.result)
txn.result = {};
request.onsuccess = function (event) {
debug("Request successful. Record count:" + event.target.result.length);
for (let i in event.target.result)
txn.result[event.target.result[i].id] = this.makeExport(event.target.result[i]);
}.bind(this);
}
},
// Will be replaced by _findWithIndex once all searchfields are added.
_findWithSearch: function _findWithSearch(txn, store, options) {
debug("_findWithSearch:" + options.filterValue + options.filterOp)
store.getAll().onsuccess = function (event) {
debug("Request successful." + event.target.result);
txn.result = event.target.result.filter(function (record) {
let properties = record.properties;
for (let i = 0; i < options.filterBy.length; i++) {
let field = options.filterBy[i];
if (!properties[field])
continue;
let value = '';
switch (field) {
case "name":
case "familyName":
case "givenName":
case "nickname":
case "email":
case "tel":
case "note":
value = [f for each (f in [properties[field]])].join("\n") || '';
break;
default:
value = properties[field];
debug("unknown field: " + field);
}
let match = false;
switch (options.filterOp) {
case "icontains":
match = value.toLowerCase().indexOf(options.filterValue.toLowerCase()) != -1;
break;
case "contains":
match = value.indexOf(options.filterValue) != -1;
break;
case "equals":
match = value == options.filterValue;
break
}
if (match)
return true;
}
return false;
}).map(this.makeExport.bind(this));
}.bind(this);
},
_findAll: function _findAll(txn, store, options) {
debug("ContactDB:_findAll: " + JSON.stringify(options));
if (!txn.result)
txn.result = {};
store.getAll(null, options.filterLimit).onsuccess = function (event) {
debug("Request successful. Record count:", event.target.result.length);
for (let i in event.target.result)
txn.result[event.target.result[i].id] = this.makeExport(event.target.result[i]);
}.bind(this);
}
};

View File

@ -0,0 +1,96 @@
/* 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/. */
"use strict"
let DEBUG = 0;
if (DEBUG)
debug = function (s) { dump("-*- Fallback ContactService component: " + s + "\n"); }
else
debug = function (s) {}
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
let EXPORTED_SYMBOLS = ["DOMContactManager"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/ContactDB.jsm");
let myGlobal = this;
let DOMContactManager = {
init: function() {
debug("Init");
this._mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
this._messages = ["Contacts:Find", "Contacts:Clear", "Contact:Save", "Contact:Remove"];
this._messages.forEach((function(msgName) {
this._mm.addMessageListener(msgName, this);
}).bind(this));
var idbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"].getService(Ci.nsIIndexedDatabaseManager);
idbManager.initWindowless(myGlobal);
this._db = new ContactDB(myGlobal);
Services.obs.addObserver(this, "profile-before-change", false);
try {
let hosts = Services.prefs.getCharPref("dom.mozContacts.whitelist")
hosts.split(",").forEach(function(aHost) {
debug("Add host: " + JSON.stringify(aHost));
if (aHost.length > 0)
Services.perms.add(Services.io.newURI(aHost, null, null), "webcontacts-manage",
Ci.nsIPermissionManager.ALLOW_ACTION);
});
} catch(e) { debug(e); }
},
observe: function(aSubject, aTopic, aData) {
myGlobal = null;
this._messages.forEach((function(msgName) {
this._mm.removeMessageListener(msgName, this);
}).bind(this));
Services.obs.removeObserver(this, "profile-before-change");
this._mm = null;
this._messages = null;
if (this._db)
this._db.close();
},
receiveMessage: function(aMessage) {
debug("Fallback DOMContactManager::receiveMessage " + aMessage.name);
let msg = aMessage.json;
switch (aMessage.name) {
case "Contacts:Find":
let result = new Array();
this._db.find(
function(contacts) {
for (let i in contacts)
result.push(contacts[i]);
debug("result:" + JSON.stringify(result));
this._mm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result});
}.bind(this),
function(aErrorMsg) { this._mm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }) }.bind(this),
msg.findOptions);
break;
case "Contact:Save":
this._db.saveContact(msg.contact, function() {this._mm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID }); }.bind(this),
function(aErrorMsg) { this._mm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
break;
case "Contact:Remove":
this._db.removeContact(msg.id,
function() {this._mm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID }); }.bind(this),
function(aErrorMsg) {this._mm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
break;
case "Contacts:Clear":
this._db.clear(function() { this._mm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this),
function(aErrorMsg) { this._mm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
}
}
}
DOMContactManager.init();

View File

@ -0,0 +1,28 @@
# 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/.
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = dom/contacts/tests
include $(DEPTH)/config/autoconf.mk
DIRS = \
$(NULL)
include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
test_contacts_basics.html \
$(NULL)
_CHROME_TEST_FILES = \
$(NULL)
libs:: $(_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)

View File

@ -0,0 +1,666 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id={674720}
-->
<head>
<title>Test for Bug {674720} WebContacts</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={674720}">Mozilla Bug {674720}</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
"use strict"
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
Components.classes["@mozilla.org/permissionmanager;1"]
.getService(Components.interfaces.nsIPermissionManager)
.add(SpecialPowers.getDocumentURIObject(window.document),
"webcontacts-manage",
Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
var adr1 = {
streetAddress: "street 1",
locality: "locality 1",
region: "region 1",
postalCode: "postal code 1",
countryName: "country 1"
};
var adr2 = {
streetAddress: "street2",
locality: "locality2",
region: "region2",
postalCode: "postal code2",
countryName: "country2"
};
var properties1 = {
name: "Testname1",
familyName: "TestFamilyName",
givenName: ["Test1","Test2"],
nickname: "nicktest",
tel: ["123456"],
adr: adr1
};
var properties2 = {
name: ["dummyName", "dummyName2"],
familyName: "dummyFamilyName",
givenName: "dummyGivenName",
honorificPrefix: ["dummyHonorificPrefix","dummyHonorificPrefix2"],
honorificSuffix: "dummyHonorificSuffix",
additionalName: "dummyadditionalName",
nickname: "dummyNickname",
tel: ["123456789", "234567890"],
email: ["a@b.c", "b@c.d"],
adr: [adr1, adr2],
impp: ["im1", "im2"],
org: ["org1", "org2"],
bday: new Date("1980, 12, 01"),
note: "test note",
photo: ["pic1", "pic2"],
category: ["cat1", "cat2"],
url: ["www.1.com", "www.2.com"],
anniversary: new Date("2000, 12, 01"),
sex: "male",
genderIdentity: "test"
};
var sample_id1;
var sample_id2;
var createResult1;
var createResult2;
var findResult1;
var findResult2;
function clearTemps() {
sample_id1 = null;
sample_id2 = null;
createResult1 = null;
createResult2 = null;
findResult1 = null;
findResult2 = null;
}
function onUnwantedSuccess() {
ok(false, "onUnwantedSuccess: shouldn't get here");
}
function onFailure() {
ok(false, "in on Failure!");
}
function checkStr(str1, str2, msg) {
if (str1)
ok(typeof str1 == "string" ? [str1] : str1, (typeof str2 == "string") ? [str2] : str2, msg);
}
function checkAddress(adr1, adr2) {
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 checkContacts(contact1, contact2) {
checkStr(contact1.name, contact2.name, "Same name");
checkStr(contact1.honorificPrefix, contact2.honorificPrefix, "Same honorificPrefix");
checkStr(contact1.givenName, contact2.givenName, "Same givenName");
checkStr(contact1.additionalName, contact2.additionalName, "Same additionalName");
checkStr(contact1.familyName, contact2.familyName, "Same familyName");
checkStr(contact1.honorificSuffix, contact2.honorificSuffix, "Same honorificSuffix");
checkStr(contact1.nickname, contact2.nickname, "Same nickname");
checkStr(contact1.email, contact2.email, "Same email");
checkStr(contact1.photo, contact2.photo, "Same photo");
checkStr(contact1.url, contact2.url, "Same url");
checkStr(contact1.category, contact2.category, "Same category");
is(contact1.bday ? contact1.bday.valueOf() : null, contact2.bday ? contact2.bday.valueOf() : null, "Same birthday");
checkStr(contact1.note, contact2.note, "Same note");
checkStr(contact1.impp, contact2.impp, "Same impp");
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 (var i in contact1.adr)
checkAddress(contact1.adr[i], contact2.adr[i]);
}
var req;
var index = 0;
var mozContacts = window.navigator.mozContacts
var steps = [
function () {
ok(true, "Deleting database");
req = mozContacts.clear();
req.onsuccess = function () {
ok(true, "Deleted the database");
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving all contacts");
req = mozContacts.find({});
req.onsuccess = function () {
ok(req.result.length == 0, "Empty database is empty.");
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Adding empty contact");
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;
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving all contacts");
req = mozContacts.find({});
req.onsuccess = function () {
ok(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({});
req2.onsuccess = function () {
ok(req2.result.length == 0, "Empty Database.");
clearTemps();
next();
}
req2.onerror = onFailure;
}
req.onerror = onFailure;
},
function () {
ok(true, "Adding a new contact1");
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;
checkContacts(properties1, createResult1);
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving all contacts");
req = mozContacts.find({});
req.onsuccess = function() {
ok(req.result.length == 1, "Found exactly 1 contact.");
findResult1 = req.result[0];
ok(findResult1.id == sample_id1, "Same ID");
checkContacts(createResult1, findResult1);
ok(findResult1.updated, "Has updated field");
ok(findResult1.published, "Has published field");
next();
}
req.onerror = onFailure;
},
function () {
ok(true, "Modifying contact1");
findResult1.impp = properties1.impp = (["phil impp"]);
req = navigator.mozContacts.save(findResult1);
req.onsuccess = function () {
var req2 = mozContacts.find({});
req2.onsuccess = function() {
ok(req2.result.length == 1, "Found exactly 1 contact.");
findResult2 = req2.result[0];
ok(findResult2.id == sample_id1, "Same ID");
checkContacts(findResult2, properties1);
ok(findResult2.impp.length == 1, "Found exactly 1 IMS info.");
next();
};
req2.onerror = onFailure;
};
req.onerror = onFailure;
},
function() {
ok(true, "Saving old contact, should abort!");
req = mozContacts.save(createResult1);
req.onsuccess = onUnwantedSuccess;
req.onerror = function() { ok(true, "Successfully declined updating old contact!"); next(); };
},
function() {
ok(true, "Saving old contact, should abort!");
req = mozContacts.save(findResult1)
req.onsuccess = onUnwantedSuccess;
req.onerror = function() { ok(true, "Successfully declined updating old contact!"); next(); };
},
function () {
ok(true, "Retrieving a specific contact by ID");
var options = {filterBy: ["id"],
filterOp: "equals",
filterValue: sample_id1};
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");
checkContacts(createResult1, properties1);
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving a specific contact by givenName");
var options = {filterBy: ["givenName"],
filterOp: "equals",
filterValue: properties1.givenName[0]};
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");
checkContacts(findResult1, createResult1);
next();
}
req.onerror = onFailure;
},
function () {
ok(true, "Modifying contact2");
findResult1.impp = properties1.impp = (["phil impp"]);
req = mozContacts.save(findResult1);
req.onsuccess = function () {
var req2 = mozContacts.find({});
req2.onsuccess = function () {
ok(req2.result.length == 1, "Found exactly 1 contact.");
findResult1 = req2.result[0];
ok(findResult1.id == sample_id1, "Same ID");
checkContacts(findResult1, createResult1);
ok(findResult1.impp.length == 1, "Found exactly 1 IMS info.");
next();
}
req2.onerror = onFailure;
};
req.onerror = onFailure;
},
function () {
ok(true, "Searching contacts by query");
var options = {filterBy: ["name", "email"],
filterOp: "icontains",
filterValue: properties1.name[0].substring(0,4)};
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");
checkContacts(findResult1, createResult1);
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Searching contacts by query");
var options = {filterBy: ["nickname", "email"],
filterOp: "icontains",
filterValue: properties1.nickname};
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");
checkContacts(findResult1, createResult1);
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Searching contacts with multiple indices");
var options = {filterBy: ["nickname", "email", "name"],
filterOp: "equals",
filterValue: properties1.nickname};
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");
checkContacts(findResult1, createResult1);
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Modifying contact3");
findResult1.email = (properties1.nickname);
findResult1.nickname = "TEST";
var newContact = new mozContact();
newContact.init(findResult1);
req = mozContacts.save(newContact);
req.onsuccess = function () {
var options = {filterBy: ["nickname", "email", "name"],
filterOp: "equals",
filterValue: properties1.nickname};
// One contact has it in nickname and the other in email
var req2 = mozContacts.find(options);
req2.onsuccess = function () {
ok(req2.result.length == 2, "Found exactly 2 contacts.");
ok(req2.result[0].id != req2.result[1].id, "Different ID");
next();
}
req2.onerror = onFailure;
};
req.onerror = onFailure;
},
function () {
ok(true, "Deleting contact" + findResult1.id);
req = mozContacts.remove(findResult1);
req.onsuccess = function () {
var req2 = mozContacts.find({});
req2.onsuccess = function () {
ok(req2.result.length == 1, "One contact left.");
findResult1 = req2.result[0];
next();
}
req2.onerror = onFailure;
}
req.onerror = onFailure;
},
function () {
ok(true, "Deleting database");
req = mozContacts.remove(findResult1);
req.onsuccess = function () {
clearTemps();
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Adding a new contact");
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();
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({})
req.onsuccess = function () {
ok(req.result.length == 2, "Found exactly 2 contact.");
next();
}
req.onerror = onFailure;
},
function () {
console.log("Searching contacts by query1");
var options = {filterBy: ["name", "email"],
filterOp: "icontains",
filterValue: properties1.name[0].substring(0, 4)}
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");
checkContacts(findResult1, createResult1);
next();
}
req.onerror = onFailure;
},
function () {
ok(true, "Searching contacts by query2");
var options = {filterBy: ["name", "email"],
filterOp: "icontains",
filterValue: properties2.name[0].substring(0, 4)};
req = mozContacts.find(options);
req.onsuccess = function () {
ok(req.result.length == 1, "Found exactly 1 contact.");
findResult1 = req.result[0];
ok(findResult1.adr.length == 2, "Adr length 2");
checkContacts(findResult1, createResult2);
next();
}
req.onerror = onFailure;
},
function () {
ok(true, "Searching contacts by tel");
var options = {filterBy: ["tel"],
filterOp: "contains",
filterValue: properties2.tel[0].substring(0, 7)};
req = mozContacts.find(options);
req.onsuccess = function () {
ok(req.result.length == 1, "Found exactly 1 contact.");
findResult1 = req.result[0];
ok(findResult1.id == sample_id2, "Same ID");
checkContacts(findResult1, createResult2);
next();
}
req.onerror = onFailure;
},
function () {
ok(true, "Searching contacts by email");
var options = {filterBy: ["email"],
filterOp: "contains",
filterValue: properties2.email[0].substring(0, 4)};
req = mozContacts.find(options);
req.onsuccess = function () {
ok(req.result.length == 1, "Found exactly 1 contact.");
findResult1 = req.result[0];
ok(findResult1.id == sample_id2, "Same ID");
checkContacts(findResult1, createResult2);
next();
}
req.onerror = onFailure;
},
function () {
ok(true, "Deleting database");
req = mozContacts.clear();
req.onsuccess = function () {
clearTemps();
next();
}
req.onerror = onFailure;
},
function () {
ok(true, "Adding 100 contacts");
for (var i=0; i<99; i++) {
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();
createResult1.init(properties1);
req = mozContacts.save(createResult1);
req.onsuccess = function () {
ok(createResult1.id, "The contact now has an ID.");
ok(createResult1.name == properties1.name, "Same Name");
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving all contacts");
req = mozContacts.find({});
req.onsuccess = function () {
ok(req.result.length == 100, "100 Entries.");
next();
}
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving all contacts with limit 10");
var options = { filterLimit: 10 };
req = mozContacts.find(options);
req.onsuccess = function () {
ok(req.result.length == 10, "10 Entries.");
next();
}
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving all contacts2");
var options = {filterBy: ["name"],
filterOp: "icontains",
filterValue: properties2.name[0].substring(0, 4)};
req = mozContacts.find({});
req.onsuccess = function () {
ok(req.result.length == 100, "100 Entries.");
checkContacts(createResult1, req.result[99]);
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();
createResult1.init(properties1);
req = mozContacts.save(createResult1);
req.onsuccess = function () {
ok(createResult1.id, "The contact now has an ID.");
ok(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 = "new email!";
cloned.givenName = "Tom";
req = mozContacts.save(cloned);
req.onsuccess = function () {
ok(cloned.id, "The contact now has an ID.");
ok(cloned.email == "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: ["name"],
filterOp: "icontains",
filterValue: properties2.name[0].substring(0, 4)};
req = mozContacts.find({});
req.onsuccess = function () {
ok(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();
createResult1.init({name: "XXX", nickname: "XXX", email: "XXX", tel: "XXX"});
req = mozContacts.save(createResult1);
req.onsuccess = function() {
var options = {filterBy: [],
filterOp: "equals",
filterValue: "XXX"};
var req2 = mozContacts.find(options);
req2.onsuccess = function() {
ok(req2.result.length == 1, "1 Entry");
next();
}
req2.onerror = onFailure;
}
req.onerror = onFailure;
},
function () {
ok(true, "Deleting database");
req = mozContacts.clear()
req.onsuccess = function () {
ok(true, "Deleted the database");
next();
}
req.onerror = onFailure;
},
function () {
ok(true, "all done!\n");
clearTemps();
SimpleTest.finish();
}
];
function next() {
ok(true, "Begin!");
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
steps[index]();
} catch(ex) {
ok(false, "Caught exception", ex);
}
index += 1;
}
function permissionTest() {
if (gContactsEnabled) {
next();
} else {
is(mozContacts, null, "mozContacts is null when not enabled.");
SimpleTest.finish();
}
}
var gContactsEnabled = SpecialPowers.getBoolPref("dom.mozContacts.enabled");
SimpleTest.waitForExplicitFinish();
addLoadEvent(permissionTest);
ok(true, "test passed");
</script>
</pre>
</body>
</html>

View File

@ -4,6 +4,7 @@ DOM_SRCDIRS = \
dom/power \
dom/network/src \
dom/sms/src \
dom/contacts \
dom/src/events \
dom/src/storage \
dom/src/offline \

View File

@ -0,0 +1,21 @@
# 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/.
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = dom
XPIDL_MODULE = dom_contacts
GRE_MODULE = 1
XPIDLSRCS = \
nsIDOMContactProperties.idl \
nsIDOMContactManager.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,33 @@
/* 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 "nsIDOMContactProperties.idl"
interface nsIArray;
interface nsIDOMContactFindOptions;
interface nsIDOMContactProperties;
interface nsIDOMDOMRequest;
[scriptable, uuid(da0f7040-388b-11e1-b86c-0800200c9a66)]
interface nsIDOMContact : nsIDOMContactProperties
{
attribute DOMString id;
readonly attribute jsval published;
readonly attribute jsval updated;
void init(in nsIDOMContactProperties properties); // Workaround BUG 723206
};
[scriptable, uuid(50a820b0-ced0-11e0-9572-0800200c9a66)]
interface nsIDOMContactManager : nsISupports
{
nsIDOMDOMRequest find(in nsIDOMContactFindOptions options);
nsIDOMDOMRequest clear();
nsIDOMDOMRequest save(in nsIDOMContact contact);
nsIDOMDOMRequest remove(in nsIDOMContact contact);
};

View File

@ -0,0 +1,52 @@
/* 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"
interface nsIArray;
interface nsIDOMContact;
[scriptable, uuid(27a568b0-cee1-11e0-9572-0800200c9a66)]
interface nsIDOMContactAddress : nsISupports
{
attribute DOMString streetAddress;
attribute DOMString locality;
attribute DOMString region;
attribute DOMString postalCode;
attribute DOMString countryName;
};
[scriptable, uuid(e31daea0-0cb6-11e1-be50-0800200c9a66)]
interface nsIDOMContactFindOptions : nsISupports
{
attribute DOMString filterValue; // e.g. "Tom"
attribute DOMString filterOp; // e.g. "contains"
attribute jsval filterBy; // DOMString[], e.g. ["givenName", "nickname"]
attribute unsigned long filterLimit;
};
[scriptable, uuid(53ed7c20-ceda-11e0-9572-0800200c9a66)]
interface nsIDOMContactProperties : 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; // DOMString[]
attribute jsval photo; // DOMString[]
attribute jsval url; // DOMString[]
attribute jsval category; // DOMString[]
attribute jsval adr; // ContactAddress[]
attribute jsval tel; // DOMString[]
attribute jsval org; // DOMString[]
attribute jsval bday; // Date
attribute jsval note; // DOMString[]
attribute jsval impp; // DOMString[]
attribute jsval anniversary; // Date
attribute jsval sex; // DOMString
attribute jsval genderIdentity; // DOMString
};

View File

@ -93,6 +93,7 @@ SHARED_LIBRARY_LIBS = \
$(DEPTH)/view/src/$(LIB_PREFIX)gkview_s.$(LIB_SUFFIX) \
$(DEPTH)/dom/base/$(LIB_PREFIX)jsdombase_s.$(LIB_SUFFIX) \
$(DEPTH)/dom/battery/$(LIB_PREFIX)dom_battery_s.$(LIB_SUFFIX) \
$(DEPTH)/dom/contacts/$(LIB_PREFIX)jsdomcontacts_s.$(LIB_SUFFIX) \
$(DEPTH)/dom/power/$(LIB_PREFIX)dom_power_s.$(LIB_SUFFIX) \
$(DEPTH)/dom/network/src/$(LIB_PREFIX)dom_network_s.$(LIB_SUFFIX) \
$(DEPTH)/dom/sms/src/$(LIB_PREFIX)dom_sms_s.$(LIB_SUFFIX) \
@ -258,6 +259,7 @@ LOCAL_INCLUDES += -I$(srcdir)/../base \
-I$(topsrcdir)/dom/src/storage \
-I$(topsrcdir)/dom/src/offline \
-I$(topsrcdir)/dom/src/geolocation \
-I$(topsrcdir)/dom/contacts \
-I$(topsrcdir)/dom/telephony \
-I. \
-I$(topsrcdir)/editor/libeditor/base \

View File

@ -3439,6 +3439,10 @@ pref("dom.battery.enabled", true);
pref("dom.sms.enabled", false);
pref("dom.sms.whitelist", "");
// WebContacts
pref("dom.mozContacts.enabled", false);
pref("dom.mozContacts.whitelist", "");
// enable JS dump() function.
pref("browser.dom.window.dump.enabled", false);