dom/contacts/ContactManager.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const DEBUG = false;
     8 function debug(s) { dump("-*- ContactManager: " + s + "\n"); }
    10 const Cc = Components.classes;
    11 const Ci = Components.interfaces;
    12 const Cu = Components.utils;
    14 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    15 Cu.import("resource://gre/modules/Services.jsm");
    16 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
    18 XPCOMUtils.defineLazyServiceGetter(Services, "DOMRequest",
    19                                    "@mozilla.org/dom/dom-request-service;1",
    20                                    "nsIDOMRequestService");
    22 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
    23                                    "@mozilla.org/childprocessmessagemanager;1",
    24                                    "nsIMessageSender");
    26 const CONTACTS_SENDMORE_MINIMUM = 5;
    28 // We need this to create a copy of the mozContact object in ContactManager.save
    29 // Keep in sync with the interfaces.
    30 const PROPERTIES = [
    31   "name", "honorificPrefix", "givenName", "additionalName", "familyName",
    32   "phoneticGivenName", "phoneticFamilyName",
    33   "honorificSuffix", "nickname", "photo", "category", "org", "jobTitle",
    34   "bday", "note", "anniversary", "sex", "genderIdentity", "key", "adr", "email",
    35   "url", "impp", "tel"
    36 ];
    38 let mozContactInitWarned = false;
    40 function Contact() { }
    42 Contact.prototype = {
    43   __init: function(aProp) {
    44     for (let prop in aProp) {
    45       this[prop] = aProp[prop];
    46     }
    47   },
    49   init: function(aProp) {
    50     // init is deprecated, warn once in the console if it's used
    51     if (!mozContactInitWarned) {
    52       mozContactInitWarned = true;
    53       Cu.reportError("mozContact.init is DEPRECATED. Use the mozContact constructor instead. " +
    54                      "See https://developer.mozilla.org/docs/WebAPI/Contacts for details.");
    55     }
    57     for (let prop of PROPERTIES) {
    58       this[prop] = aProp[prop];
    59     }
    60   },
    62   setMetadata: function(aId, aPublished, aUpdated) {
    63     this.id = aId;
    64     if (aPublished) {
    65       this.published = aPublished;
    66     }
    67     if (aUpdated) {
    68       this.updated = aUpdated;
    69     }
    70   },
    72   classID: Components.ID("{72a5ee28-81d8-4af8-90b3-ae935396cc66}"),
    73   contractID: "@mozilla.org/contact;1",
    74   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
    75 };
    77 function ContactManager() { }
    79 ContactManager.prototype = {
    80   __proto__: DOMRequestIpcHelper.prototype,
    81   hasListenPermission: false,
    82   _cachedContacts: [] ,
    84   set oncontactchange(aHandler) {
    85     this.__DOM_IMPL__.setEventHandler("oncontactchange", aHandler);
    86   },
    88   get oncontactchange() {
    89     return this.__DOM_IMPL__.getEventHandler("oncontactchange");
    90   },
    92   _convertContact: function(aContact) {
    93     let newContact = new this._window.mozContact(aContact.properties);
    94     newContact.setMetadata(aContact.id, aContact.published, aContact.updated);
    95     return newContact;
    96   },
    98   _convertContacts: function(aContacts) {
    99     let contacts = new this._window.Array();
   100     for (let i in aContacts) {
   101       contacts.push(this._convertContact(aContacts[i]));
   102     }
   103     return contacts;
   104   },
   106   _fireSuccessOrDone: function(aCursor, aResult) {
   107     if (aResult == null) {
   108       Services.DOMRequest.fireDone(aCursor);
   109     } else {
   110       Services.DOMRequest.fireSuccess(aCursor, aResult);
   111     }
   112   },
   114   _pushArray: function(aArr1, aArr2) {
   115     aArr1.push.apply(aArr1, aArr2);
   116   },
   118   receiveMessage: function(aMessage) {
   119     if (DEBUG) debug("receiveMessage: " + aMessage.name);
   120     let msg = aMessage.json;
   121     let contacts = msg.contacts;
   123     let req;
   124     switch (aMessage.name) {
   125       case "Contacts:Find:Return:OK":
   126         req = this.getRequest(msg.requestID);
   127         if (req) {
   128           let result = this._convertContacts(contacts);
   129           Services.DOMRequest.fireSuccess(req.request, result);
   130         } else {
   131           if (DEBUG) debug("no request stored!" + msg.requestID);
   132         }
   133         break;
   134       case "Contacts:GetAll:Next":
   135         let data = this.getRequest(msg.cursorId);
   136         if (!data) {
   137           break;
   138         }
   139         let result = contacts ? this._convertContacts(contacts) : [null];
   140         if (data.waitingForNext) {
   141           if (DEBUG) debug("cursor waiting for contact, sending");
   142           data.waitingForNext = false;
   143           let contact = result.shift();
   144           this._pushArray(data.cachedContacts, result);
   145           this.nextTick(this._fireSuccessOrDone.bind(this, data.cursor, contact));
   146           if (!contact) {
   147             this.removeRequest(msg.cursorId);
   148           }
   149         } else {
   150           if (DEBUG) debug("cursor not waiting, saving");
   151           this._pushArray(data.cachedContacts, result);
   152         }
   153         break;
   154       case "Contact:Save:Return:OK":
   155         // If a cached contact was saved and a new contact ID was returned, update the contact's ID
   156         if (this._cachedContacts[msg.requestID]) {
   157           if (msg.contactID) {
   158             this._cachedContacts[msg.requestID].id = msg.contactID;
   159           }
   160           delete this._cachedContacts[msg.requestID];
   161         }
   162       case "Contacts:Clear:Return:OK":
   163       case "Contact:Remove:Return:OK":
   164         req = this.getRequest(msg.requestID);
   165         if (req)
   166           Services.DOMRequest.fireSuccess(req.request, null);
   167         break;
   168       case "Contacts:Find:Return:KO":
   169       case "Contact:Save:Return:KO":
   170       case "Contact:Remove:Return:KO":
   171       case "Contacts:Clear:Return:KO":
   172       case "Contacts:GetRevision:Return:KO":
   173       case "Contacts:Count:Return:KO":
   174         req = this.getRequest(msg.requestID);
   175         if (req) {
   176           if (req.request) {
   177             req = req.request;
   178           }
   179           Services.DOMRequest.fireError(req, msg.errorMsg);
   180         }
   181         break;
   182       case "Contacts:GetAll:Return:KO":
   183         req = this.getRequest(msg.requestID);
   184         if (req) {
   185           Services.DOMRequest.fireError(req.cursor, msg.errorMsg);
   186         }
   187         break;
   188       case "PermissionPromptHelper:AskPermission:OK":
   189         if (DEBUG) debug("id: " + msg.requestID);
   190         req = this.getRequest(msg.requestID);
   191         if (!req) {
   192           break;
   193         }
   195         if (msg.result == Ci.nsIPermissionManager.ALLOW_ACTION) {
   196           req.allow();
   197         } else {
   198           req.cancel();
   199         }
   200         break;
   201       case "Contact:Changed":
   202         // Fire oncontactchange event
   203         if (DEBUG) debug("Contacts:ContactChanged: " + msg.contactID + ", " + msg.reason);
   204         let event = new this._window.MozContactChangeEvent("contactchange", {
   205           contactID: msg.contactID,
   206           reason: msg.reason
   207         });
   208         this.dispatchEvent(event);
   209         break;
   210       case "Contacts:Revision":
   211         if (DEBUG) debug("new revision: " + msg.revision);
   212         req = this.getRequest(msg.requestID);
   213         if (req) {
   214           Services.DOMRequest.fireSuccess(req.request, msg.revision);
   215         }
   216         break;
   217       case "Contacts:Count":
   218         if (DEBUG) debug("count: " + msg.count);
   219         req = this.getRequest(msg.requestID);
   220         if (req) {
   221           Services.DOMRequest.fireSuccess(req.request, msg.count);
   222         }
   223         break;
   224       default:
   225         if (DEBUG) debug("Wrong message: " + aMessage.name);
   226     }
   227     this.removeRequest(msg.requestID);
   228   },
   230   dispatchEvent: function(event) {
   231     if (this.hasListenPermission) {
   232       this.__DOM_IMPL__.dispatchEvent(event);
   233     }
   234   },
   236   askPermission: function (aAccess, aRequest, aAllowCallback, aCancelCallback) {
   237     if (DEBUG) debug("askPermission for contacts");
   238     let access;
   239     switch(aAccess) {
   240       case "create":
   241         access = "create";
   242         break;
   243       case "update":
   244       case "remove":
   245         access = "write";
   246         break;
   247       case "find":
   248       case "listen":
   249       case "revision":
   250       case "count":
   251         access = "read";
   252         break;
   253       default:
   254         access = "unknown";
   255       }
   257     // Shortcut for ALLOW_ACTION so we avoid a parent roundtrip
   258     let type = "contacts-" + access;
   259     let permValue =
   260       Services.perms.testExactPermissionFromPrincipal(this._window.document.nodePrincipal, type);
   261     if (permValue == Ci.nsIPermissionManager.ALLOW_ACTION) {
   262       aAllowCallback();
   263       return;
   264     }
   266     let requestID = this.getRequestId({
   267       request: aRequest,
   268       allow: function() {
   269         aAllowCallback();
   270       }.bind(this),
   271       cancel : function() {
   272         if (aCancelCallback) {
   273           aCancelCallback()
   274         } else if (aRequest) {
   275           Services.DOMRequest.fireError(aRequest, "Not Allowed");
   276         }
   277       }.bind(this)
   278     });
   280     let principal = this._window.document.nodePrincipal;
   281     cpmm.sendAsyncMessage("PermissionPromptHelper:AskPermission", {
   282       type: "contacts",
   283       access: access,
   284       requestID: requestID,
   285       origin: principal.origin,
   286       appID: principal.appId,
   287       browserFlag: principal.isInBrowserElement,
   288       windowID: this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID
   289     });
   290   },
   292   save: function save(aContact) {
   293     // We have to do a deep copy of the contact manually here because
   294     // nsFrameMessageManager doesn't know how to create a structured clone of a
   295     // mozContact object.
   296     let newContact = {properties: {}};
   298     try {
   299       for (let field of PROPERTIES) {
   300         // This hack makes sure modifications to the sequence attributes get validated.
   301         aContact[field] = aContact[field];
   302         newContact.properties[field] = aContact[field];
   303       }
   304     } catch (e) {
   305       // And then make sure we throw a proper error message (no internal file and line #)
   306       throw new this._window.DOMError(e.name, e.message);
   307     }
   309     let request = this.createRequest();
   310     let requestID = this.getRequestId({request: request});
   312     let reason;
   313     if (aContact.id == "undefined") {
   314       // for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes
   315       // 25c00f0190e5c545b4d421E2ddbab9e0
   316       aContact.id = this._getRandomId().replace(/[{}-]/g, "");
   317       // Cache the contact so that its ID may be updated later if necessary
   318       this._cachedContacts[requestID] = aContact;
   319       reason = "create";
   320     } else {
   321       reason = "update";
   322     }
   324     newContact.id = aContact.id;
   325     newContact.published = aContact.published;
   326     newContact.updated = aContact.updated;
   328     if (DEBUG) debug("send: " + JSON.stringify(newContact));
   330     let options = { contact: newContact, reason: reason };
   331     let allowCallback = function() {
   332       cpmm.sendAsyncMessage("Contact:Save", {requestID: requestID, options: options});
   333     }.bind(this)
   334     this.askPermission(reason, request, allowCallback);
   335     return request;
   336   },
   338   find: function(aOptions) {
   339     if (DEBUG) debug("find! " + JSON.stringify(aOptions));
   340     let request = this.createRequest();
   341     let options = { findOptions: aOptions };
   342     let allowCallback = function() {
   343       cpmm.sendAsyncMessage("Contacts:Find", {requestID: this.getRequestId({request: request, reason: "find"}), options: options});
   344     }.bind(this)
   345     this.askPermission("find", request, allowCallback);
   346     return request;
   347   },
   349   createCursor: function CM_createCursor(aRequest) {
   350     let data = {
   351       cursor: Services.DOMRequest.createCursor(this._window, function() {
   352         this.handleContinue(id);
   353       }.bind(this)),
   354       cachedContacts: [],
   355       waitingForNext: true,
   356     };
   357     let id = this.getRequestId(data);
   358     if (DEBUG) debug("saved cursor id: " + id);
   359     return [id, data.cursor];
   360   },
   362   getAll: function CM_getAll(aOptions) {
   363     if (DEBUG) debug("getAll: " + JSON.stringify(aOptions));
   364     let [cursorId, cursor] = this.createCursor();
   365     let allowCallback = function() {
   366       cpmm.sendAsyncMessage("Contacts:GetAll", {
   367         cursorId: cursorId, findOptions: aOptions});
   368     }.bind(this);
   369     this.askPermission("find", cursor, allowCallback);
   370     return cursor;
   371   },
   373   nextTick: function nextTick(aCallback) {
   374     Services.tm.currentThread.dispatch(aCallback, Ci.nsIThread.DISPATCH_NORMAL);
   375   },
   377   handleContinue: function CM_handleContinue(aCursorId) {
   378     if (DEBUG) debug("handleContinue: " + aCursorId);
   379     let data = this.getRequest(aCursorId);
   380     if (data.cachedContacts.length > 0) {
   381       if (DEBUG) debug("contact in cache");
   382       let contact = data.cachedContacts.shift();
   383       this.nextTick(this._fireSuccessOrDone.bind(this, data.cursor, contact));
   384       if (!contact) {
   385         this.removeRequest(aCursorId);
   386       } else if (data.cachedContacts.length === CONTACTS_SENDMORE_MINIMUM) {
   387         cpmm.sendAsyncMessage("Contacts:GetAll:SendNow", { cursorId: aCursorId });
   388       }
   389     } else {
   390       if (DEBUG) debug("waiting for contact");
   391       data.waitingForNext = true;
   392     }
   393   },
   395   remove: function removeContact(aRecordOrId) {
   396     let request = this.createRequest();
   397     let id;
   398     if (typeof aRecordOrId === "string") {
   399       id = aRecordOrId;
   400     } else if (!aRecordOrId || !aRecordOrId.id) {
   401       Services.DOMRequest.fireErrorAsync(request, true);
   402       return request;
   403     } else {
   404       id = aRecordOrId.id;
   405     }
   407     let options = { id: id };
   408     let allowCallback = function() {
   409       cpmm.sendAsyncMessage("Contact:Remove", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options});
   410     }.bind(this);
   411     this.askPermission("remove", request, allowCallback);
   412     return request;
   413   },
   415   clear: function() {
   416     if (DEBUG) debug("clear");
   417     let request = this.createRequest();
   418     let options = {};
   419     let allowCallback = function() {
   420       cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options});
   421     }.bind(this);
   422     this.askPermission("remove", request, allowCallback);
   423     return request;
   424   },
   426   getRevision: function() {
   427     let request = this.createRequest();
   429     let allowCallback = function() {
   430       cpmm.sendAsyncMessage("Contacts:GetRevision", {
   431         requestID: this.getRequestId({ request: request })
   432       });
   433     }.bind(this);
   435     let cancelCallback = function() {
   436       Services.DOMRequest.fireError(request);
   437     };
   439     this.askPermission("revision", request, allowCallback, cancelCallback);
   440     return request;
   441   },
   443   getCount: function() {
   444     let request = this.createRequest();
   446     let allowCallback = function() {
   447       cpmm.sendAsyncMessage("Contacts:GetCount", {
   448         requestID: this.getRequestId({ request: request })
   449       });
   450     }.bind(this);
   452     let cancelCallback = function() {
   453       Services.DOMRequest.fireError(request);
   454     };
   456     this.askPermission("count", request, allowCallback, cancelCallback);
   457     return request;
   458   },
   460   init: function(aWindow) {
   461     // DOMRequestIpcHelper.initHelper sets this._window
   462     this.initDOMRequestHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO",
   463                               "Contacts:Clear:Return:OK", "Contacts:Clear:Return:KO",
   464                               "Contact:Save:Return:OK", "Contact:Save:Return:KO",
   465                               "Contact:Remove:Return:OK", "Contact:Remove:Return:KO",
   466                               "Contact:Changed",
   467                               "PermissionPromptHelper:AskPermission:OK",
   468                               "Contacts:GetAll:Next", "Contacts:GetAll:Return:KO",
   469                               "Contacts:Count",
   470                               "Contacts:Revision", "Contacts:GetRevision:Return:KO",]);
   473     let allowCallback = function() {
   474       cpmm.sendAsyncMessage("Contacts:RegisterForMessages");
   475       this.hasListenPermission = true;
   476     }.bind(this);
   478     this.askPermission("listen", null, allowCallback);
   479   },
   481   classID: Components.ID("{8beb3a66-d70a-4111-b216-b8e995ad3aff}"),
   482   contractID: "@mozilla.org/contactManager;1",
   483   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
   484                                          Ci.nsIObserver,
   485                                          Ci.nsIDOMGlobalPropertyInitializer]),
   486 };
   488 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
   489   Contact, ContactManager
   490 ]);

mercurial