Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 | this.EXPORTED_SYMBOLS = ["SessionFile"]; |
michael@0 | 8 | |
michael@0 | 9 | /** |
michael@0 | 10 | * Implementation of all the disk I/O required by the session store. |
michael@0 | 11 | * This is a private API, meant to be used only by the session store. |
michael@0 | 12 | * It will change. Do not use it for any other purpose. |
michael@0 | 13 | * |
michael@0 | 14 | * Note that this module implicitly depends on one of two things: |
michael@0 | 15 | * 1. either the asynchronous file I/O system enqueues its requests |
michael@0 | 16 | * and never attempts to simultaneously execute two I/O requests on |
michael@0 | 17 | * the files used by this module from two distinct threads; or |
michael@0 | 18 | * 2. the clients of this API are well-behaved and do not place |
michael@0 | 19 | * concurrent requests to the files used by this module. |
michael@0 | 20 | * |
michael@0 | 21 | * Otherwise, we could encounter bugs, especially under Windows, |
michael@0 | 22 | * e.g. if a request attempts to write sessionstore.js while |
michael@0 | 23 | * another attempts to copy that file. |
michael@0 | 24 | * |
michael@0 | 25 | * This implementation uses OS.File, which guarantees property 1. |
michael@0 | 26 | */ |
michael@0 | 27 | |
michael@0 | 28 | const Cu = Components.utils; |
michael@0 | 29 | const Cc = Components.classes; |
michael@0 | 30 | const Ci = Components.interfaces; |
michael@0 | 31 | const Cr = Components.results; |
michael@0 | 32 | |
michael@0 | 33 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 34 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 35 | Cu.import("resource://gre/modules/osfile.jsm"); |
michael@0 | 36 | Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this); |
michael@0 | 37 | Cu.import("resource://gre/modules/Promise.jsm"); |
michael@0 | 38 | Cu.import("resource://gre/modules/AsyncShutdown.jsm"); |
michael@0 | 39 | |
michael@0 | 40 | XPCOMUtils.defineLazyModuleGetter(this, "console", |
michael@0 | 41 | "resource://gre/modules/devtools/Console.jsm"); |
michael@0 | 42 | XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", |
michael@0 | 43 | "resource://gre/modules/TelemetryStopwatch.jsm"); |
michael@0 | 44 | XPCOMUtils.defineLazyModuleGetter(this, "Task", |
michael@0 | 45 | "resource://gre/modules/Task.jsm"); |
michael@0 | 46 | XPCOMUtils.defineLazyServiceGetter(this, "Telemetry", |
michael@0 | 47 | "@mozilla.org/base/telemetry;1", "nsITelemetry"); |
michael@0 | 48 | |
michael@0 | 49 | this.SessionFile = { |
michael@0 | 50 | /** |
michael@0 | 51 | * Read the contents of the session file, asynchronously. |
michael@0 | 52 | */ |
michael@0 | 53 | read: function () { |
michael@0 | 54 | return SessionFileInternal.read(); |
michael@0 | 55 | }, |
michael@0 | 56 | /** |
michael@0 | 57 | * Write the contents of the session file, asynchronously. |
michael@0 | 58 | */ |
michael@0 | 59 | write: function (aData) { |
michael@0 | 60 | return SessionFileInternal.write(aData); |
michael@0 | 61 | }, |
michael@0 | 62 | /** |
michael@0 | 63 | * Gather telemetry statistics. |
michael@0 | 64 | * |
michael@0 | 65 | * |
michael@0 | 66 | * Most of the work is done off the main thread but there is a main |
michael@0 | 67 | * thread cost involved to send data to the worker thread. This method |
michael@0 | 68 | * should therefore be called only when we know that it will not disrupt |
michael@0 | 69 | * the user's experience, e.g. on idle-daily. |
michael@0 | 70 | * |
michael@0 | 71 | * @return {Promise} |
michael@0 | 72 | * @promise {object} An object holding all the information to be submitted |
michael@0 | 73 | * to Telemetry. |
michael@0 | 74 | */ |
michael@0 | 75 | gatherTelemetry: function(aData) { |
michael@0 | 76 | return SessionFileInternal.gatherTelemetry(aData); |
michael@0 | 77 | }, |
michael@0 | 78 | /** |
michael@0 | 79 | * Create a backup copy, asynchronously. |
michael@0 | 80 | * This is designed to perform backup on upgrade. |
michael@0 | 81 | */ |
michael@0 | 82 | createBackupCopy: function (ext) { |
michael@0 | 83 | return SessionFileInternal.createBackupCopy(ext); |
michael@0 | 84 | }, |
michael@0 | 85 | /** |
michael@0 | 86 | * Remove a backup copy, asynchronously. |
michael@0 | 87 | * This is designed to clean up a backup on upgrade. |
michael@0 | 88 | */ |
michael@0 | 89 | removeBackupCopy: function (ext) { |
michael@0 | 90 | return SessionFileInternal.removeBackupCopy(ext); |
michael@0 | 91 | }, |
michael@0 | 92 | /** |
michael@0 | 93 | * Wipe the contents of the session file, asynchronously. |
michael@0 | 94 | */ |
michael@0 | 95 | wipe: function () { |
michael@0 | 96 | SessionFileInternal.wipe(); |
michael@0 | 97 | } |
michael@0 | 98 | }; |
michael@0 | 99 | |
michael@0 | 100 | Object.freeze(SessionFile); |
michael@0 | 101 | |
michael@0 | 102 | /** |
michael@0 | 103 | * Utilities for dealing with promises and Task.jsm |
michael@0 | 104 | */ |
michael@0 | 105 | let SessionFileInternal = { |
michael@0 | 106 | /** |
michael@0 | 107 | * The path to sessionstore.js |
michael@0 | 108 | */ |
michael@0 | 109 | path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"), |
michael@0 | 110 | |
michael@0 | 111 | /** |
michael@0 | 112 | * The path to sessionstore.bak |
michael@0 | 113 | */ |
michael@0 | 114 | backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"), |
michael@0 | 115 | |
michael@0 | 116 | /** |
michael@0 | 117 | * The promise returned by the latest call to |write|. |
michael@0 | 118 | * We use it to ensure that AsyncShutdown.profileBeforeChange cannot |
michael@0 | 119 | * interrupt a call to |write|. |
michael@0 | 120 | */ |
michael@0 | 121 | _latestWrite: null, |
michael@0 | 122 | |
michael@0 | 123 | /** |
michael@0 | 124 | * |true| once we have decided to stop receiving write instructiosn |
michael@0 | 125 | */ |
michael@0 | 126 | _isClosed: false, |
michael@0 | 127 | |
michael@0 | 128 | read: function () { |
michael@0 | 129 | // We must initialize the worker during startup so it will be ready to |
michael@0 | 130 | // perform the final write. If shutdown happens soon after startup and |
michael@0 | 131 | // the worker has not started yet we may not write. |
michael@0 | 132 | // See Bug 964531. |
michael@0 | 133 | SessionWorker.post("init"); |
michael@0 | 134 | |
michael@0 | 135 | return Task.spawn(function*() { |
michael@0 | 136 | for (let filename of [this.path, this.backupPath]) { |
michael@0 | 137 | try { |
michael@0 | 138 | let startMs = Date.now(); |
michael@0 | 139 | |
michael@0 | 140 | let data = yield OS.File.read(filename, { encoding: "utf-8" }); |
michael@0 | 141 | |
michael@0 | 142 | Telemetry.getHistogramById("FX_SESSION_RESTORE_READ_FILE_MS") |
michael@0 | 143 | .add(Date.now() - startMs); |
michael@0 | 144 | |
michael@0 | 145 | return data; |
michael@0 | 146 | } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) { |
michael@0 | 147 | // Ignore exceptions about non-existent files. |
michael@0 | 148 | } |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | return ""; |
michael@0 | 152 | }.bind(this)); |
michael@0 | 153 | }, |
michael@0 | 154 | |
michael@0 | 155 | gatherTelemetry: function(aStateString) { |
michael@0 | 156 | return Task.spawn(function() { |
michael@0 | 157 | let msg = yield SessionWorker.post("gatherTelemetry", [aStateString]); |
michael@0 | 158 | this._recordTelemetry(msg.telemetry); |
michael@0 | 159 | throw new Task.Result(msg.telemetry); |
michael@0 | 160 | }.bind(this)); |
michael@0 | 161 | }, |
michael@0 | 162 | |
michael@0 | 163 | write: function (aData) { |
michael@0 | 164 | if (this._isClosed) { |
michael@0 | 165 | return Promise.reject(new Error("SessionFile is closed")); |
michael@0 | 166 | } |
michael@0 | 167 | let refObj = {}; |
michael@0 | 168 | |
michael@0 | 169 | let isFinalWrite = false; |
michael@0 | 170 | if (Services.startup.shuttingDown) { |
michael@0 | 171 | // If shutdown has started, we will want to stop receiving |
michael@0 | 172 | // write instructions. |
michael@0 | 173 | isFinalWrite = this._isClosed = true; |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | return this._latestWrite = Task.spawn(function task() { |
michael@0 | 177 | TelemetryStopwatch.start("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj); |
michael@0 | 178 | |
michael@0 | 179 | try { |
michael@0 | 180 | let promise = SessionWorker.post("write", [aData]); |
michael@0 | 181 | // At this point, we measure how long we stop the main thread |
michael@0 | 182 | TelemetryStopwatch.finish("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj); |
michael@0 | 183 | |
michael@0 | 184 | // Now wait for the result and record how long the write took |
michael@0 | 185 | let msg = yield promise; |
michael@0 | 186 | this._recordTelemetry(msg.telemetry); |
michael@0 | 187 | } catch (ex) { |
michael@0 | 188 | TelemetryStopwatch.cancel("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj); |
michael@0 | 189 | console.error("Could not write session state file ", this.path, ex); |
michael@0 | 190 | } |
michael@0 | 191 | |
michael@0 | 192 | if (isFinalWrite) { |
michael@0 | 193 | Services.obs.notifyObservers(null, "sessionstore-final-state-write-complete", ""); |
michael@0 | 194 | } |
michael@0 | 195 | }.bind(this)); |
michael@0 | 196 | }, |
michael@0 | 197 | |
michael@0 | 198 | createBackupCopy: function (ext) { |
michael@0 | 199 | return SessionWorker.post("createBackupCopy", [ext]); |
michael@0 | 200 | }, |
michael@0 | 201 | |
michael@0 | 202 | removeBackupCopy: function (ext) { |
michael@0 | 203 | return SessionWorker.post("removeBackupCopy", [ext]); |
michael@0 | 204 | }, |
michael@0 | 205 | |
michael@0 | 206 | wipe: function () { |
michael@0 | 207 | SessionWorker.post("wipe"); |
michael@0 | 208 | }, |
michael@0 | 209 | |
michael@0 | 210 | _recordTelemetry: function(telemetry) { |
michael@0 | 211 | for (let id of Object.keys(telemetry)){ |
michael@0 | 212 | let value = telemetry[id]; |
michael@0 | 213 | let samples = []; |
michael@0 | 214 | if (Array.isArray(value)) { |
michael@0 | 215 | samples.push(...value); |
michael@0 | 216 | } else { |
michael@0 | 217 | samples.push(value); |
michael@0 | 218 | } |
michael@0 | 219 | let histogram = Telemetry.getHistogramById(id); |
michael@0 | 220 | for (let sample of samples) { |
michael@0 | 221 | histogram.add(sample); |
michael@0 | 222 | } |
michael@0 | 223 | } |
michael@0 | 224 | } |
michael@0 | 225 | }; |
michael@0 | 226 | |
michael@0 | 227 | // Interface to a dedicated thread handling I/O |
michael@0 | 228 | let SessionWorker = (function () { |
michael@0 | 229 | let worker = new PromiseWorker("resource:///modules/sessionstore/SessionWorker.js", |
michael@0 | 230 | OS.Shared.LOG.bind("SessionWorker")); |
michael@0 | 231 | return { |
michael@0 | 232 | post: function post(...args) { |
michael@0 | 233 | let promise = worker.post.apply(worker, args); |
michael@0 | 234 | return promise.then( |
michael@0 | 235 | null, |
michael@0 | 236 | function onError(error) { |
michael@0 | 237 | // Decode any serialized error |
michael@0 | 238 | if (error instanceof PromiseWorker.WorkerError) { |
michael@0 | 239 | throw OS.File.Error.fromMsg(error.data); |
michael@0 | 240 | } |
michael@0 | 241 | // Extract something meaningful from ErrorEvent |
michael@0 | 242 | if (error instanceof ErrorEvent) { |
michael@0 | 243 | throw new Error(error.message, error.filename, error.lineno); |
michael@0 | 244 | } |
michael@0 | 245 | throw error; |
michael@0 | 246 | } |
michael@0 | 247 | ); |
michael@0 | 248 | } |
michael@0 | 249 | }; |
michael@0 | 250 | })(); |
michael@0 | 251 | |
michael@0 | 252 | // Ensure that we can write sessionstore.js cleanly before the profile |
michael@0 | 253 | // becomes unaccessible. |
michael@0 | 254 | AsyncShutdown.profileBeforeChange.addBlocker( |
michael@0 | 255 | "SessionFile: Finish writing the latest sessionstore.js", |
michael@0 | 256 | function() { |
michael@0 | 257 | return SessionFileInternal._latestWrite; |
michael@0 | 258 | }); |