services/healthreport/profile.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
michael@0 3 * file, 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 #ifndef MERGED_COMPARTMENT
michael@0 8
michael@0 9 this.EXPORTED_SYMBOLS = [
michael@0 10 "ProfileCreationTimeAccessor",
michael@0 11 "ProfileMetadataProvider",
michael@0 12 ];
michael@0 13
michael@0 14 const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
michael@0 15
michael@0 16 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
michael@0 17
michael@0 18 Cu.import("resource://gre/modules/Metrics.jsm");
michael@0 19
michael@0 20 #endif
michael@0 21
michael@0 22 const DEFAULT_PROFILE_MEASUREMENT_NAME = "age";
michael@0 23 const REQUIRED_UINT32_TYPE = {type: "TYPE_UINT32"};
michael@0 24
michael@0 25 Cu.import("resource://gre/modules/Promise.jsm");
michael@0 26 Cu.import("resource://gre/modules/osfile.jsm")
michael@0 27 Cu.import("resource://gre/modules/Task.jsm");
michael@0 28 Cu.import("resource://gre/modules/Log.jsm");
michael@0 29 Cu.import("resource://services-common/utils.js");
michael@0 30
michael@0 31 // Profile creation time access.
michael@0 32 // This is separate from the provider to simplify testing and enable extraction
michael@0 33 // to a shared location in the future.
michael@0 34 this.ProfileCreationTimeAccessor = function(profile, log) {
michael@0 35 this.profilePath = profile || OS.Constants.Path.profileDir;
michael@0 36 if (!this.profilePath) {
michael@0 37 throw new Error("No profile directory.");
michael@0 38 }
michael@0 39 this._log = log || {"debug": function (s) { dump(s + "\n"); }};
michael@0 40 }
michael@0 41 this.ProfileCreationTimeAccessor.prototype = {
michael@0 42 /**
michael@0 43 * There are three ways we can get our creation time:
michael@0 44 *
michael@0 45 * 1. From our own saved value (to avoid redundant work).
michael@0 46 * 2. From the on-disk JSON file.
michael@0 47 * 3. By calculating it from the filesystem.
michael@0 48 *
michael@0 49 * If we have to calculate, we write out the file; if we have
michael@0 50 * to touch the file, we persist in-memory.
michael@0 51 *
michael@0 52 * @return a promise that resolves to the profile's creation time.
michael@0 53 */
michael@0 54 get created() {
michael@0 55 if (this._created) {
michael@0 56 return Promise.resolve(this._created);
michael@0 57 }
michael@0 58
michael@0 59 function onSuccess(times) {
michael@0 60 if (times && times.created) {
michael@0 61 return this._created = times.created;
michael@0 62 }
michael@0 63 return onFailure.call(this, null, times);
michael@0 64 }
michael@0 65
michael@0 66 function onFailure(err, times) {
michael@0 67 return this.computeAndPersistTimes(times)
michael@0 68 .then(function onSuccess(created) {
michael@0 69 return this._created = created;
michael@0 70 }.bind(this));
michael@0 71 }
michael@0 72
michael@0 73 return this.readTimes()
michael@0 74 .then(onSuccess.bind(this),
michael@0 75 onFailure.bind(this));
michael@0 76 },
michael@0 77
michael@0 78 /**
michael@0 79 * Explicitly make `file`, a filename, a full path
michael@0 80 * relative to our profile path.
michael@0 81 */
michael@0 82 getPath: function (file) {
michael@0 83 return OS.Path.join(this.profilePath, file);
michael@0 84 },
michael@0 85
michael@0 86 /**
michael@0 87 * Return a promise which resolves to the JSON contents
michael@0 88 * of the time file in this accessor's profile.
michael@0 89 */
michael@0 90 readTimes: function (file="times.json") {
michael@0 91 return CommonUtils.readJSON(this.getPath(file));
michael@0 92 },
michael@0 93
michael@0 94 /**
michael@0 95 * Return a promise representing the writing of `contents`
michael@0 96 * to `file` in the specified profile.
michael@0 97 */
michael@0 98 writeTimes: function (contents, file="times.json") {
michael@0 99 return CommonUtils.writeJSON(contents, this.getPath(file));
michael@0 100 },
michael@0 101
michael@0 102 /**
michael@0 103 * Merge existing contents with a 'created' field, writing them
michael@0 104 * to the specified file. Promise, naturally.
michael@0 105 */
michael@0 106 computeAndPersistTimes: function (existingContents, file="times.json") {
michael@0 107 let path = this.getPath(file);
michael@0 108 function onOldest(oldest) {
michael@0 109 let contents = existingContents || {};
michael@0 110 contents.created = oldest;
michael@0 111 return this.writeTimes(contents, path)
michael@0 112 .then(function onSuccess() {
michael@0 113 return oldest;
michael@0 114 });
michael@0 115 }
michael@0 116
michael@0 117 return this.getOldestProfileTimestamp()
michael@0 118 .then(onOldest.bind(this));
michael@0 119 },
michael@0 120
michael@0 121 /**
michael@0 122 * Traverse the contents of the profile directory, finding the oldest file
michael@0 123 * and returning its creation timestamp.
michael@0 124 */
michael@0 125 getOldestProfileTimestamp: function () {
michael@0 126 let self = this;
michael@0 127 let oldest = Date.now() + 1000;
michael@0 128 let iterator = new OS.File.DirectoryIterator(this.profilePath);
michael@0 129 self._log.debug("Iterating over profile " + this.profilePath);
michael@0 130 if (!iterator) {
michael@0 131 throw new Error("Unable to fetch oldest profile entry: no profile iterator.");
michael@0 132 }
michael@0 133
michael@0 134 function onEntry(entry) {
michael@0 135 function onStatSuccess(info) {
michael@0 136 // OS.File doesn't seem to be behaving. See Bug 827148.
michael@0 137 // Let's do the best we can. This whole function is defensive.
michael@0 138 let date = info.winBirthDate || info.macBirthDate;
michael@0 139 if (!date || !date.getTime()) {
michael@0 140 // OS.File will only return file creation times of any kind on Mac
michael@0 141 // and Windows, where birthTime is defined.
michael@0 142 // That means we're unable to function on Linux, so we use mtime
michael@0 143 // instead.
michael@0 144 self._log.debug("No birth date. Using mtime.");
michael@0 145 date = info.lastModificationDate;
michael@0 146 }
michael@0 147
michael@0 148 if (date) {
michael@0 149 let timestamp = date.getTime();
michael@0 150 self._log.debug("Using date: " + entry.path + " = " + date);
michael@0 151 if (timestamp < oldest) {
michael@0 152 oldest = timestamp;
michael@0 153 }
michael@0 154 }
michael@0 155 }
michael@0 156
michael@0 157 function onStatFailure(e) {
michael@0 158 // Never mind.
michael@0 159 self._log.debug("Stat failure: " + CommonUtils.exceptionStr(e));
michael@0 160 }
michael@0 161
michael@0 162 return OS.File.stat(entry.path)
michael@0 163 .then(onStatSuccess, onStatFailure);
michael@0 164 }
michael@0 165
michael@0 166 let promise = iterator.forEach(onEntry);
michael@0 167
michael@0 168 function onSuccess() {
michael@0 169 iterator.close();
michael@0 170 return oldest;
michael@0 171 }
michael@0 172
michael@0 173 function onFailure(reason) {
michael@0 174 iterator.close();
michael@0 175 throw new Error("Unable to fetch oldest profile entry: " + reason);
michael@0 176 }
michael@0 177
michael@0 178 return promise.then(onSuccess, onFailure);
michael@0 179 },
michael@0 180 }
michael@0 181
michael@0 182 /**
michael@0 183 * Measurements pertaining to the user's profile.
michael@0 184 */
michael@0 185 function ProfileMetadataMeasurement() {
michael@0 186 Metrics.Measurement.call(this);
michael@0 187 }
michael@0 188 ProfileMetadataMeasurement.prototype = {
michael@0 189 __proto__: Metrics.Measurement.prototype,
michael@0 190
michael@0 191 name: DEFAULT_PROFILE_MEASUREMENT_NAME,
michael@0 192 version: 1,
michael@0 193
michael@0 194 fields: {
michael@0 195 // Profile creation date. Number of days since Unix epoch.
michael@0 196 profileCreation: {type: Metrics.Storage.FIELD_LAST_NUMERIC},
michael@0 197 },
michael@0 198 };
michael@0 199
michael@0 200 /**
michael@0 201 * Turn a millisecond timestamp into a day timestamp.
michael@0 202 *
michael@0 203 * @param msec a number of milliseconds since epoch.
michael@0 204 * @return the number of whole days denoted by the input.
michael@0 205 */
michael@0 206 function truncate(msec) {
michael@0 207 return Math.floor(msec / MILLISECONDS_PER_DAY);
michael@0 208 }
michael@0 209
michael@0 210 /**
michael@0 211 * A Metrics.Provider for profile metadata, such as profile creation time.
michael@0 212 */
michael@0 213 this.ProfileMetadataProvider = function() {
michael@0 214 Metrics.Provider.call(this);
michael@0 215 }
michael@0 216 this.ProfileMetadataProvider.prototype = {
michael@0 217 __proto__: Metrics.Provider.prototype,
michael@0 218
michael@0 219 name: "org.mozilla.profile",
michael@0 220
michael@0 221 measurementTypes: [ProfileMetadataMeasurement],
michael@0 222
michael@0 223 pullOnly: true,
michael@0 224
michael@0 225 getProfileCreationDays: function () {
michael@0 226 let accessor = new ProfileCreationTimeAccessor(null, this._log);
michael@0 227
michael@0 228 return accessor.created
michael@0 229 .then(truncate);
michael@0 230 },
michael@0 231
michael@0 232 collectConstantData: function () {
michael@0 233 let m = this.getMeasurement(DEFAULT_PROFILE_MEASUREMENT_NAME, 1);
michael@0 234
michael@0 235 return Task.spawn(function collectConstant() {
michael@0 236 let createdDays = yield this.getProfileCreationDays();
michael@0 237
michael@0 238 yield this.enqueueStorageOperation(function storeDays() {
michael@0 239 return m.setLastNumeric("profileCreation", createdDays);
michael@0 240 });
michael@0 241 }.bind(this));
michael@0 242 },
michael@0 243 };
michael@0 244

mercurial