dom/activities/src/ActivitiesService.jsm

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict"
michael@0 6
michael@0 7 const Cu = Components.utils;
michael@0 8 const Cc = Components.classes;
michael@0 9 const Ci = Components.interfaces;
michael@0 10
michael@0 11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 12 Cu.import("resource://gre/modules/Services.jsm");
michael@0 13 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
michael@0 14
michael@0 15 XPCOMUtils.defineLazyModuleGetter(this, "ActivitiesServiceFilter",
michael@0 16 "resource://gre/modules/ActivitiesServiceFilter.jsm");
michael@0 17
michael@0 18 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
michael@0 19 "@mozilla.org/parentprocessmessagemanager;1",
michael@0 20 "nsIMessageBroadcaster");
michael@0 21
michael@0 22 XPCOMUtils.defineLazyServiceGetter(this, "NetUtil",
michael@0 23 "@mozilla.org/network/util;1",
michael@0 24 "nsINetUtil");
michael@0 25
michael@0 26 this.EXPORTED_SYMBOLS = [];
michael@0 27
michael@0 28 function debug(aMsg) {
michael@0 29 //dump("-- ActivitiesService.jsm " + Date.now() + " " + aMsg + "\n");
michael@0 30 }
michael@0 31
michael@0 32 const DB_NAME = "activities";
michael@0 33 const DB_VERSION = 1;
michael@0 34 const STORE_NAME = "activities";
michael@0 35
michael@0 36 function ActivitiesDb() {
michael@0 37
michael@0 38 }
michael@0 39
michael@0 40 ActivitiesDb.prototype = {
michael@0 41 __proto__: IndexedDBHelper.prototype,
michael@0 42
michael@0 43 init: function actdb_init() {
michael@0 44 this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME]);
michael@0 45 },
michael@0 46
michael@0 47 /**
michael@0 48 * Create the initial database schema.
michael@0 49 *
michael@0 50 * The schema of records stored is as follows:
michael@0 51 *
michael@0 52 * {
michael@0 53 * id: String
michael@0 54 * manifest: String
michael@0 55 * name: String
michael@0 56 * icon: String
michael@0 57 * description: jsval
michael@0 58 * }
michael@0 59 */
michael@0 60 upgradeSchema: function actdb_upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
michael@0 61 debug("Upgrade schema " + aOldVersion + " -> " + aNewVersion);
michael@0 62 let objectStore = aDb.createObjectStore(STORE_NAME, { keyPath: "id" });
michael@0 63
michael@0 64 // indexes
michael@0 65 objectStore.createIndex("name", "name", { unique: false });
michael@0 66 objectStore.createIndex("manifest", "manifest", { unique: false });
michael@0 67
michael@0 68 debug("Created object stores and indexes");
michael@0 69 },
michael@0 70
michael@0 71 // unique ids made of (uri, action)
michael@0 72 createId: function actdb_createId(aObject) {
michael@0 73 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 74 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 75 converter.charset = "UTF-8";
michael@0 76
michael@0 77 let hasher = Cc["@mozilla.org/security/hash;1"]
michael@0 78 .createInstance(Ci.nsICryptoHash);
michael@0 79 hasher.init(hasher.SHA1);
michael@0 80
michael@0 81 // add uri and action to the hash
michael@0 82 ["manifest", "name"].forEach(function(aProp) {
michael@0 83 let data = converter.convertToByteArray(aObject[aProp], {});
michael@0 84 hasher.update(data, data.length);
michael@0 85 });
michael@0 86
michael@0 87 return hasher.finish(true);
michael@0 88 },
michael@0 89
michael@0 90 // Add all the activities carried in the |aObjects| array.
michael@0 91 add: function actdb_add(aObjects, aSuccess, aError) {
michael@0 92 this.newTxn("readwrite", STORE_NAME, function (txn, store) {
michael@0 93 aObjects.forEach(function (aObject) {
michael@0 94 let object = {
michael@0 95 manifest: aObject.manifest,
michael@0 96 name: aObject.name,
michael@0 97 icon: aObject.icon || "",
michael@0 98 description: aObject.description
michael@0 99 };
michael@0 100 object.id = this.createId(object);
michael@0 101 debug("Going to add " + JSON.stringify(object));
michael@0 102 store.put(object);
michael@0 103 }, this);
michael@0 104 }.bind(this), aSuccess, aError);
michael@0 105 },
michael@0 106
michael@0 107 // Remove all the activities carried in the |aObjects| array.
michael@0 108 remove: function actdb_remove(aObjects) {
michael@0 109 this.newTxn("readwrite", STORE_NAME, function (txn, store) {
michael@0 110 aObjects.forEach(function (aObject) {
michael@0 111 let object = {
michael@0 112 manifest: aObject.manifest,
michael@0 113 name: aObject.name
michael@0 114 };
michael@0 115 debug("Going to remove " + JSON.stringify(object));
michael@0 116 store.delete(this.createId(object));
michael@0 117 }, this);
michael@0 118 }.bind(this), function() {}, function() {});
michael@0 119 },
michael@0 120
michael@0 121 find: function actdb_find(aObject, aSuccess, aError, aMatch) {
michael@0 122 debug("Looking for " + aObject.options.name);
michael@0 123
michael@0 124 this.newTxn("readonly", STORE_NAME, function (txn, store) {
michael@0 125 let index = store.index("name");
michael@0 126 let request = index.mozGetAll(aObject.options.name);
michael@0 127 request.onsuccess = function findSuccess(aEvent) {
michael@0 128 debug("Request successful. Record count: " + aEvent.target.result.length);
michael@0 129 if (!txn.result) {
michael@0 130 txn.result = {
michael@0 131 name: aObject.options.name,
michael@0 132 options: []
michael@0 133 };
michael@0 134 }
michael@0 135
michael@0 136 aEvent.target.result.forEach(function(result) {
michael@0 137 if (!aMatch(result))
michael@0 138 return;
michael@0 139
michael@0 140 txn.result.options.push({
michael@0 141 manifest: result.manifest,
michael@0 142 icon: result.icon,
michael@0 143 description: result.description
michael@0 144 });
michael@0 145 });
michael@0 146 }
michael@0 147 }.bind(this), aSuccess, aError);
michael@0 148 }
michael@0 149 }
michael@0 150
michael@0 151 let Activities = {
michael@0 152 messages: [
michael@0 153 // ActivityProxy.js
michael@0 154 "Activity:Start",
michael@0 155
michael@0 156 // ActivityWrapper.js
michael@0 157 "Activity:Ready",
michael@0 158
michael@0 159 // ActivityRequestHandler.js
michael@0 160 "Activity:PostResult",
michael@0 161 "Activity:PostError",
michael@0 162
michael@0 163 "Activities:Register",
michael@0 164 "Activities:Unregister",
michael@0 165 "Activities:GetContentTypes",
michael@0 166
michael@0 167 "child-process-shutdown"
michael@0 168 ],
michael@0 169
michael@0 170 init: function activities_init() {
michael@0 171 this.messages.forEach(function(msgName) {
michael@0 172 ppmm.addMessageListener(msgName, this);
michael@0 173 }, this);
michael@0 174
michael@0 175 Services.obs.addObserver(this, "xpcom-shutdown", false);
michael@0 176
michael@0 177 this.db = new ActivitiesDb();
michael@0 178 this.db.init();
michael@0 179 this.callers = {};
michael@0 180 },
michael@0 181
michael@0 182 observe: function activities_observe(aSubject, aTopic, aData) {
michael@0 183 this.messages.forEach(function(msgName) {
michael@0 184 ppmm.removeMessageListener(msgName, this);
michael@0 185 }, this);
michael@0 186 ppmm = null;
michael@0 187
michael@0 188 if (this.db) {
michael@0 189 this.db.close();
michael@0 190 this.db = null;
michael@0 191 }
michael@0 192
michael@0 193 Services.obs.removeObserver(this, "xpcom-shutdown");
michael@0 194 },
michael@0 195
michael@0 196 /**
michael@0 197 * Starts an activity by doing:
michael@0 198 * - finds a list of matching activities.
michael@0 199 * - calls the UI glue to get the user choice.
michael@0 200 * - fire an system message of type "activity" to this app, sending the
michael@0 201 * activity data as a payload.
michael@0 202 */
michael@0 203 startActivity: function activities_startActivity(aMsg) {
michael@0 204 debug("StartActivity: " + JSON.stringify(aMsg));
michael@0 205
michael@0 206 let successCb = function successCb(aResults) {
michael@0 207 debug(JSON.stringify(aResults));
michael@0 208
michael@0 209 // We have no matching activity registered, let's fire an error.
michael@0 210 if (aResults.options.length === 0) {
michael@0 211 Activities.callers[aMsg.id].mm.sendAsyncMessage("Activity:FireError", {
michael@0 212 "id": aMsg.id,
michael@0 213 "error": "NO_PROVIDER"
michael@0 214 });
michael@0 215 delete Activities.callers[aMsg.id];
michael@0 216 return;
michael@0 217 }
michael@0 218
michael@0 219 function getActivityChoice(aChoice) {
michael@0 220 debug("Activity choice: " + aChoice);
michael@0 221
michael@0 222 // The user has cancelled the choice, fire an error.
michael@0 223 if (aChoice === -1) {
michael@0 224 Activities.callers[aMsg.id].mm.sendAsyncMessage("Activity:FireError", {
michael@0 225 "id": aMsg.id,
michael@0 226 "error": "ActivityCanceled"
michael@0 227 });
michael@0 228 delete Activities.callers[aMsg.id];
michael@0 229 return;
michael@0 230 }
michael@0 231
michael@0 232 let sysmm = Cc["@mozilla.org/system-message-internal;1"]
michael@0 233 .getService(Ci.nsISystemMessagesInternal);
michael@0 234 if (!sysmm) {
michael@0 235 // System message is not present, what should we do?
michael@0 236 delete Activities.callers[aMsg.id];
michael@0 237 return;
michael@0 238 }
michael@0 239
michael@0 240 debug("Sending system message...");
michael@0 241 let result = aResults.options[aChoice];
michael@0 242 sysmm.sendMessage("activity", {
michael@0 243 "id": aMsg.id,
michael@0 244 "payload": aMsg.options,
michael@0 245 "target": result.description
michael@0 246 },
michael@0 247 Services.io.newURI(result.description.href, null, null),
michael@0 248 Services.io.newURI(result.manifest, null, null),
michael@0 249 {
michael@0 250 "manifestURL": Activities.callers[aMsg.id].manifestURL,
michael@0 251 "pageURL": Activities.callers[aMsg.id].pageURL
michael@0 252 });
michael@0 253
michael@0 254 if (!result.description.returnValue) {
michael@0 255 Activities.callers[aMsg.id].mm.sendAsyncMessage("Activity:FireSuccess", {
michael@0 256 "id": aMsg.id,
michael@0 257 "result": null
michael@0 258 });
michael@0 259 // No need to notify observers, since we don't want the caller
michael@0 260 // to be raised on the foreground that quick.
michael@0 261 delete Activities.callers[aMsg.id];
michael@0 262 }
michael@0 263 };
michael@0 264
michael@0 265 let glue = Cc["@mozilla.org/dom/activities/ui-glue;1"]
michael@0 266 .createInstance(Ci.nsIActivityUIGlue);
michael@0 267 glue.chooseActivity(aResults.name, aResults.options, getActivityChoice);
michael@0 268 };
michael@0 269
michael@0 270 let errorCb = function errorCb(aError) {
michael@0 271 // Something unexpected happened. Should we send an error back?
michael@0 272 debug("Error in startActivity: " + aError + "\n");
michael@0 273 };
michael@0 274
michael@0 275 let matchFunc = function matchFunc(aResult) {
michael@0 276 return ActivitiesServiceFilter.match(aMsg.options.data,
michael@0 277 aResult.description.filters);
michael@0 278 };
michael@0 279
michael@0 280 this.db.find(aMsg, successCb, errorCb, matchFunc);
michael@0 281 },
michael@0 282
michael@0 283 receiveMessage: function activities_receiveMessage(aMessage) {
michael@0 284 let mm = aMessage.target;
michael@0 285 let msg = aMessage.json;
michael@0 286
michael@0 287 let caller;
michael@0 288 let obsData;
michael@0 289
michael@0 290 if (aMessage.name == "Activity:PostResult" ||
michael@0 291 aMessage.name == "Activity:PostError" ||
michael@0 292 aMessage.name == "Activity:Ready") {
michael@0 293 caller = this.callers[msg.id];
michael@0 294 if (!caller) {
michael@0 295 debug("!! caller is null for msg.id=" + msg.id);
michael@0 296 return;
michael@0 297 }
michael@0 298 obsData = JSON.stringify({ manifestURL: caller.manifestURL,
michael@0 299 pageURL: caller.pageURL,
michael@0 300 success: aMessage.name == "Activity:PostResult" });
michael@0 301 }
michael@0 302
michael@0 303 switch(aMessage.name) {
michael@0 304 case "Activity:Start":
michael@0 305 this.callers[msg.id] = { mm: mm,
michael@0 306 manifestURL: msg.manifestURL,
michael@0 307 pageURL: msg.pageURL };
michael@0 308 this.startActivity(msg);
michael@0 309 break;
michael@0 310
michael@0 311 case "Activity:Ready":
michael@0 312 caller.childMM = mm;
michael@0 313 break;
michael@0 314
michael@0 315 case "Activity:PostResult":
michael@0 316 caller.mm.sendAsyncMessage("Activity:FireSuccess", msg);
michael@0 317 delete this.callers[msg.id];
michael@0 318 break;
michael@0 319 case "Activity:PostError":
michael@0 320 caller.mm.sendAsyncMessage("Activity:FireError", msg);
michael@0 321 delete this.callers[msg.id];
michael@0 322 break;
michael@0 323
michael@0 324 case "Activities:Register":
michael@0 325 let self = this;
michael@0 326 this.db.add(msg,
michael@0 327 function onSuccess(aEvent) {
michael@0 328 mm.sendAsyncMessage("Activities:Register:OK", null);
michael@0 329 let res = [];
michael@0 330 msg.forEach(function(aActivity) {
michael@0 331 self.updateContentTypeList(aActivity, res);
michael@0 332 });
michael@0 333 if (res.length) {
michael@0 334 ppmm.broadcastAsyncMessage("Activities:RegisterContentTypes",
michael@0 335 { contentTypes: res });
michael@0 336 }
michael@0 337 },
michael@0 338 function onError(aEvent) {
michael@0 339 msg.error = "REGISTER_ERROR";
michael@0 340 mm.sendAsyncMessage("Activities:Register:KO", msg);
michael@0 341 });
michael@0 342 break;
michael@0 343 case "Activities:Unregister":
michael@0 344 this.db.remove(msg);
michael@0 345 let res = [];
michael@0 346 msg.forEach(function(aActivity) {
michael@0 347 this.updateContentTypeList(aActivity, res);
michael@0 348 }, this);
michael@0 349 if (res.length) {
michael@0 350 ppmm.broadcastAsyncMessage("Activities:UnregisterContentTypes",
michael@0 351 { contentTypes: res });
michael@0 352 }
michael@0 353 break;
michael@0 354 case "Activities:GetContentTypes":
michael@0 355 this.sendContentTypes(mm);
michael@0 356 break;
michael@0 357 case "child-process-shutdown":
michael@0 358 for (let id in this.callers) {
michael@0 359 if (this.callers[id].childMM == mm) {
michael@0 360 this.callers[id].mm.sendAsyncMessage("Activity:FireError", {
michael@0 361 "id": id,
michael@0 362 "error": "ActivityCanceled"
michael@0 363 });
michael@0 364 delete this.callers[id];
michael@0 365 break;
michael@0 366 }
michael@0 367 }
michael@0 368 break;
michael@0 369 }
michael@0 370 },
michael@0 371
michael@0 372 updateContentTypeList: function updateContentTypeList(aActivity, aResult) {
michael@0 373 // Bail out if this is not a "view" activity.
michael@0 374 if (aActivity.name != "view") {
michael@0 375 return;
michael@0 376 }
michael@0 377
michael@0 378 let types = aActivity.description.filters.type;
michael@0 379 if (typeof types == "string") {
michael@0 380 types = [types];
michael@0 381 }
michael@0 382
michael@0 383 // Check that this is a real content type and sanitize it.
michael@0 384 types.forEach(function(aContentType) {
michael@0 385 let hadCharset = { };
michael@0 386 let charset = { };
michael@0 387 let contentType =
michael@0 388 NetUtil.parseContentType(aContentType, charset, hadCharset);
michael@0 389 if (contentType) {
michael@0 390 aResult.push(contentType);
michael@0 391 }
michael@0 392 });
michael@0 393 },
michael@0 394
michael@0 395 sendContentTypes: function sendContentTypes(aMm) {
michael@0 396 let res = [];
michael@0 397 let self = this;
michael@0 398 this.db.find({ options: { name: "view" } },
michael@0 399 function() { // Success callback.
michael@0 400 if (res.length) {
michael@0 401 aMm.sendAsyncMessage("Activities:RegisterContentTypes",
michael@0 402 { contentTypes: res });
michael@0 403 }
michael@0 404 },
michael@0 405 null, // Error callback.
michael@0 406 function(aActivity) { // Matching callback.
michael@0 407 self.updateContentTypeList(aActivity, res)
michael@0 408 return false;
michael@0 409 }
michael@0 410 );
michael@0 411 }
michael@0 412 }
michael@0 413
michael@0 414 Activities.init();

mercurial