Bug 780707 - Contacts API: support prompting. r=dougt

This commit is contained in:
Gregor Wagner 2012-08-09 11:34:57 -07:00
parent 599fb28214
commit fa7a5fb9cf
7 changed files with 282 additions and 110 deletions

View File

@ -63,6 +63,7 @@ DIRS += \
src \
locales \
network \
permission \
plugins/base \
plugins/ipc \
indexedDB \

View File

@ -18,6 +18,7 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
Cu.import("resource://gre/modules/PermissionPromptHelper.jsm");
XPCOMUtils.defineLazyGetter(Services, "DOMRequest", function() {
return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService);
@ -277,80 +278,20 @@ ContactManager.prototype = {
_oncontactchange: null,
set oncontactchange(aCallback) {
if (this.hasPrivileges)
debug("set oncontactchange");
let allowCallback = function() {
this._oncontactchange = aCallback;
else
}.bind(this);
let cancelCallback = function() {
throw Components.results.NS_ERROR_FAILURE;
}
this.askPermission("listen", null, allowCallback, cancelCallback);
},
get oncontactchange() {
return this._oncontactchange;
},
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: [],
jobTitle: [],
bday: null,
note: [],
impp: [],
anniversary: null,
sex: null,
genderIdentity: null
};
for (let field in newContact.properties)
newContact.properties[field] = aContact[field];
let reason;
if (aContact.id == "undefined") {
// for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes
// 25c00f0190e5c545b4d421E2ddbab9e0
aContact.id = this._getRandomId().replace('-', '', 'g').replace('{', '').replace('}', '');
reason = "create";
} else {
reason = "update";
}
this._setMetaData(newContact, aContact);
debug("send: " + JSON.stringify(newContact));
request = this.createRequest();
cpmm.sendAsyncMessage("Contact:Save", {contact: newContact,
requestID: this.getRequestId({request: request, reason: reason })});
return request;
} else {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
},
remove: function removeContact(aRecord) {
let request;
if (this.hasPrivileges) {
request = this.createRequest();
cpmm.sendAsyncMessage("Contact:Remove", {id: aRecord.id,
requestID: this.getRequestId({request: request, reason: "remove"})});
return request;
} else {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
},
_setMetaData: function(aNewContact, aRecord) {
aNewContact.id = aRecord.id;
aNewContact.published = aRecord.published;
@ -408,40 +349,144 @@ ContactManager.prototype = {
if (req)
Services.DOMRequest.fireError(req.request, msg.errorMsg);
break;
case "PermissionPromptHelper:AskPermission:OK":
debug("id: " + msg.requestID);
req = this.getRequest(msg.requestID);
if (!req) {
break;
}
if (msg.result == Ci.nsIPermissionManager.ALLOW_ACTION) {
req.allow();
} else {
req.cancel();
}
break;
default:
debug("Wrong message: " + aMessage.name);
}
this.removeRequest(msg.requestID);
},
find: function(aOptions) {
askPermission: function (aAccess, aReqeust, aAllowCallback, aCancelCallback) {
debug("askPermission for contacts");
let requestID = this.getRequestId({
request: aReqeust,
allow: function() {
aAllowCallback();
}.bind(this),
cancel : function() {
if (aCancelCallback) {
aCancelCallback()
} else if (request) {
Services.DOMRequest.fireError(request, "Not Allowed");
}
}.bind(this)
});
let principal = this._window.document.nodePrincipal;
cpmm.sendAsyncMessage("PermissionPromptHelper:AskPermission", {
type: "contacts",
access: aAccess,
requestID: requestID,
origin: principal.origin,
appID: principal.appId,
browserFlag: principal.isInBrowserElement
});
},
save: function save(aContact) {
let request;
if (this.hasPrivileges) {
request = this.createRequest();
cpmm.sendAsyncMessage("Contacts:Find", {findOptions: aOptions,
requestID: this.getRequestId({request: request, reason: "find"})});
return request;
} else {
debug("find not allowed");
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
debug("save: " + JSON.stringify(aContact) + " :" + aContact.id);
let newContact = {};
newContact.properties = {
name: [],
honorificPrefix: [],
givenName: [],
additionalName: [],
familyName: [],
honorificSuffix: [],
nickname: [],
email: [],
photo: [],
url: [],
category: [],
adr: [],
tel: [],
org: [],
jobTitle: [],
bday: null,
note: [],
impp: [],
anniversary: null,
sex: null,
genderIdentity: null
};
for (let field in newContact.properties) {
newContact.properties[field] = aContact[field];
}
let reason;
if (aContact.id == "undefined") {
// for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes
// 25c00f0190e5c545b4d421E2ddbab9e0
aContact.id = this._getRandomId().replace('-', '', 'g').replace('{', '').replace('}', '');
reason = "create";
} else {
reason = "update";
}
this._setMetaData(newContact, aContact);
debug("send: " + JSON.stringify(newContact));
request = this.createRequest();
let options = { contact: newContact };
let allowCallback = function() {
cpmm.sendAsyncMessage("Contact:Save", {requestID: this.getRequestId({request: request, reason: reason}), options: options});
}.bind(this)
this.askPermission(reason, request, allowCallback);
return request;
},
find: function(aOptions) {
debug("find! " + JSON.stringify(aOptions));
let request;
request = this.createRequest();
let options = { findOptions: aOptions };
let allowCallback = function() {
cpmm.sendAsyncMessage("Contacts:Find", {requestID: this.getRequestId({request: request, reason: "find"}), options: options});
}.bind(this)
this.askPermission("find", request, allowCallback);
return request;
},
remove: function removeContact(aRecord) {
let request;
request = this.createRequest();
let options = { id: aRecord.id };
let allowCallback = function() {
cpmm.sendAsyncMessage("Contact:Remove", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options});
}.bind(this)
this.askPermission("remove", request, allowCallback);
return request;
},
clear: function() {
debug("clear");
let request;
if (this.hasPrivileges) {
request = this.createRequest();
cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({request: request, reason: "remove"})});
return request;
} else {
debug("clear not allowed");
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
request = this.createRequest();
let options = {};
let allowCallback = function() {
cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options});
}.bind(this)
this.askPermission("remove", request, allowCallback);
return request;
},
getSimContacts: function(aType) {
let request;
if (this.hasPrivileges) {
request = this.createRequest();
let allowCallback = function() {
let callback = function(aType, aContacts) {
debug("got SIM contacts: " + aType + " " + JSON.stringify(aContacts));
let result = aContacts.map(function(c) { return { name: [c.alphaId], tel: [c.number] } });
@ -449,13 +494,15 @@ ContactManager.prototype = {
Services.DOMRequest.fireSuccess(request, result);
};
debug("getSimContacts " + aType);
request = this.createRequest();
mRIL.getICCContacts(aType, callback);
return request;
} else {
debug("getSimContacts not allowed");
}.bind(this);
let cancelCallback = function() {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
this.askPermission("getSimContacts", request, allowCallback, cancelCallback);
return request;
},
init: function(aWindow) {
@ -466,13 +513,8 @@ ContactManager.prototype = {
this.initHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO",
"Contacts:Clear:Return:OK", "Contacts:Clear:Return:KO",
"Contact:Save:Return:OK", "Contact:Save:Return:KO",
"Contact:Remove:Return:OK", "Contact:Remove:Return:KO"]);
let perm = Services.perms.testExactPermissionFromPrincipal(aWindow.document.nodePrincipal, "contacts");
//only pages with perm set can use the contacts
this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
debug("Contacts permission: " + this.hasPrivileges);
"Contact:Remove:Return:OK", "Contact:Remove:Return:KO",
"PermissionPromptHelper:AskPermission:OK"]);
},
// Called from DOMRequestIpcHelper

View File

@ -302,7 +302,6 @@ ContactDB.prototype = {
*/
find: function find(aSuccessCb, aFailureCb, aOptions) {
debug("ContactDB:find val:" + aOptions.filterValue + " by: " + aOptions.filterBy + " op: " + aOptions.filterOp + "\n");
let self = this;
this.newTxn("readonly", function (txn, store) {
if (aOptions && (aOptions.filterOp == "equals" || aOptions.filterOp == "contains")) {

View File

@ -58,7 +58,7 @@ let DOMContactManager = {
receiveMessage: function(aMessage) {
debug("Fallback DOMContactManager::receiveMessage " + aMessage.name);
let mm = aMessage.target.QueryInterface(Ci.nsIFrameMessageManager);
let msg = aMessage.json;
let msg = aMessage.data;
/*
* Sorting the contacts by sortBy field. sortBy can either be familyName or givenName.
@ -69,8 +69,10 @@ let DOMContactManager = {
let x, y;
let sortByNameSet = true;
let result = 0;
let sortBy = msg.findOptions.sortBy;
let sortOrder = msg.findOptions.sortOrder;
let findOptions = msg.options.findOptions;
let sortBy = findOptions.sortBy;
let sortOrder = findOptions.sortOrder;
if (!a.properties[sortBy] || !(x = a.properties[sortBy][0].toLowerCase())) {
sortByNameSet = false;
}
@ -107,30 +109,33 @@ let DOMContactManager = {
function(contacts) {
for (let i in contacts)
result.push(contacts[i]);
if (msg.findOptions.sortOrder !== 'undefined' && msg.findOptions.sortBy !== 'undefined') {
debug('sortBy: ' + msg.findOptions.sortBy + ', sortOrder: ' + msg.findOptions.sortOrder );
result.sort(sortfunction);
if (msg.findOptions.filterLimit)
result = result.slice(0, msg.findOptions.filterLimit);
if (msg.options && msg.options.findOptions) {
let findOptions = msg.options.findOptions;
if (findOptions.sortOrder !== 'undefined' && findOptions.sortBy !== 'undefined') {
debug('sortBy: ' + findOptions.sortBy + ', sortOrder: ' + findOptions.sortOrder );
result.sort(sortfunction);
if (findOptions.filterLimit)
result = result.slice(0, findOptions.filterLimit);
}
}
debug("result:" + JSON.stringify(result));
mm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result});
}.bind(this),
function(aErrorMsg) { mm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }) }.bind(this),
msg.findOptions);
msg.options.findOptions);
break;
case "Contact:Save":
this._db.saveContact(
msg.contact,
function() { mm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID, contactID: msg.contact.id }); }.bind(this),
msg.options.contact,
function() { mm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID, contactID: msg.options.contact.id }); }.bind(this),
function(aErrorMsg) { mm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
);
break;
case "Contact:Remove":
this._db.removeContact(
msg.id,
function() { mm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID, contactID: msg.id }); }.bind(this),
msg.options.id,
function() { mm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID, contactID: msg.options.id }); }.bind(this),
function(aErrorMsg) { mm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
);
break;
@ -139,6 +144,8 @@ let DOMContactManager = {
function() { mm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this),
function(aErrorMsg) { mm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
);
default:
debug("WRONG MESSAGE NAME: " + aMessage.name);
}
}
}

View File

@ -406,6 +406,7 @@ var steps = [
ok(true, "Adding a new contact with properties1");
createResult1 = new mozContact();
createResult1.init(properties1);
mozContacts.oncontactchange = null;
req = navigator.mozContacts.save(createResult1);
req.onsuccess = function () {
ok(createResult1.id, "The contact now has an ID.");
@ -420,7 +421,6 @@ var steps = [
var options = {filterBy: ["tel"],
filterOp: "contains",
filterValue: properties1.tel[1].number.substring(1,5)};
mozContacts.oncontactchange = null;
req = mozContacts.find(options);
req.onsuccess = function () {
ok(req.result.length == 1, "Found exactly 1 contact.");

View File

@ -0,0 +1,26 @@
# 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 = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = dom
LIBRARY_NAME = jsdompermission_s
XPIDL_MODULE = dom_permission
GRE_MODULE = 1
EXTRA_JS_MODULES = \
PermissionPromptHelper.jsm \
$(NULL)
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,97 @@
/* 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/. */
/* PermissionPromptHelper checks the permissionDB for a given permission
* name and performs prompting if needed.
* Usage: send PermissionPromptHelper:AskPermission via the FrameMessageManager with:
* |origin|, |appID|, |browserFlag| -> used for getting the principal and
* |type| and |access| to call testExactPermissionFromPrincipal.
* Note that |access| isn't currently used.
* Other arugments are:
* requestID: ID that gets returned with the result message.
*
* Once the permission is checked, it returns with the message
* "PermissionPromptHelper:AskPermission:OK"
* The result contains the |result| e.g.Ci.nsIPermissionManager.ALLOW_ACTION
* and a requestID that
*/
"use strict";
let DEBUG = 0;
if (DEBUG)
debug = function (s) { dump("-*- Permission Prompt Helper component: " + s + "\n"); }
else
debug = function (s) {}
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
let EXPORTED_SYMBOLS = ["PermissionPromptHelper"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
});
var permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
var appsService = Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService);
let PermissionPromptHelper = {
init: function() {
debug("Init");
ppmm.addMessageListener("PermissionPromptHelper:AskPermission", this);
Services.obs.addObserver(this, "profile-before-change", false);
},
askPermission: function(aMessage, aCallbacks) {
let msg = aMessage.json;
debug("askPerm: " + JSON.stringify(aMessage.json));
let uri = Services.io.newURI(msg.origin, null, null);
let principal = secMan.getAppCodebasePrincipal(uri, msg.appID, msg.browserFlag);
let perm = permissionManager.testExactPermissionFromPrincipal(principal, msg.type);
switch(perm) {
case Ci.nsIPermissionManager.ALLOW_ACTION:
aCallbacks.allow();
return;
case Ci.nsIPermissionManager.DENY_ACTION:
aCallbacks.cancel();
return;
}
// FIXXMEE PROMPT MAGIC! Bug 773114.
// We have to diplay the prompt here.
},
observe: function(aSubject, aTopic, aData) {
ppmm.removeMessageListener("PermissionPromptHelper:AskPermission", this);
Services.obs.removeObserver(this, "profile-before-change");
ppmm = null;
},
receiveMessage: function(aMessage) {
debug("PermissionPromptHelper::receiveMessage " + aMessage.name);
let mm = aMessage.target.QueryInterface(Ci.nsIFrameMessageManager);
let msg = aMessage.data;
let result;
if (aMessage.name == "PermissionPromptHelper:AskPermission") {
this.askPermission(aMessage, {
cancel: function() {
mm.sendAsyncMessage("PermissionPromptHelper:AskPermission:OK", {result: Ci.nsIPermissionManager.DENY_ACTION, requestID: msg.requestID});
},
allow: function() {
mm.sendAsyncMessage("PermissionPromptHelper:AskPermission:OK", {result: Ci.nsIPermissionManager.ALLOW_ACTION, requestID: msg.requestID});
}
});
}
}
}
PermissionPromptHelper.init();