toolkit/crashreporter/CrashSubmit.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 Components.utils.import("resource://gre/modules/Services.jsm");
michael@0 6 Components.utils.import("resource://gre/modules/KeyValueParser.jsm");
michael@0 7
michael@0 8 this.EXPORTED_SYMBOLS = [
michael@0 9 "CrashSubmit"
michael@0 10 ];
michael@0 11
michael@0 12 const Cc = Components.classes;
michael@0 13 const Ci = Components.interfaces;
michael@0 14 const STATE_START = Ci.nsIWebProgressListener.STATE_START;
michael@0 15 const STATE_STOP = Ci.nsIWebProgressListener.STATE_STOP;
michael@0 16
michael@0 17 const SUCCESS = "success";
michael@0 18 const FAILED = "failed";
michael@0 19 const SUBMITTING = "submitting";
michael@0 20
michael@0 21 let reportURL = null;
michael@0 22 let strings = null;
michael@0 23 let myListener = null;
michael@0 24
michael@0 25 function parseINIStrings(file) {
michael@0 26 var factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
michael@0 27 getService(Ci.nsIINIParserFactory);
michael@0 28 var parser = factory.createINIParser(file);
michael@0 29 var obj = {};
michael@0 30 var en = parser.getKeys("Strings");
michael@0 31 while (en.hasMore()) {
michael@0 32 var key = en.getNext();
michael@0 33 obj[key] = parser.getString("Strings", key);
michael@0 34 }
michael@0 35 return obj;
michael@0 36 }
michael@0 37
michael@0 38 // Since we're basically re-implementing part of the crashreporter
michael@0 39 // client here, we'll just steal the strings we need from crashreporter.ini
michael@0 40 function getL10nStrings() {
michael@0 41 let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
michael@0 42 getService(Ci.nsIProperties);
michael@0 43 let path = dirSvc.get("GreD", Ci.nsIFile);
michael@0 44 path.append("crashreporter.ini");
michael@0 45 if (!path.exists()) {
michael@0 46 // see if we're on a mac
michael@0 47 path = path.parent;
michael@0 48 path.append("crashreporter.app");
michael@0 49 path.append("Contents");
michael@0 50 path.append("MacOS");
michael@0 51 path.append("crashreporter.ini");
michael@0 52 if (!path.exists()) {
michael@0 53 // very bad, but I don't know how to recover
michael@0 54 return;
michael@0 55 }
michael@0 56 }
michael@0 57 let crstrings = parseINIStrings(path);
michael@0 58 strings = {
michael@0 59 'crashid': crstrings.CrashID,
michael@0 60 'reporturl': crstrings.CrashDetailsURL
michael@0 61 };
michael@0 62
michael@0 63 path = dirSvc.get("XCurProcD", Ci.nsIFile);
michael@0 64 path.append("crashreporter-override.ini");
michael@0 65 if (path.exists()) {
michael@0 66 crstrings = parseINIStrings(path);
michael@0 67 if ('CrashID' in crstrings)
michael@0 68 strings['crashid'] = crstrings.CrashID;
michael@0 69 if ('CrashDetailsURL' in crstrings)
michael@0 70 strings['reporturl'] = crstrings.CrashDetailsURL;
michael@0 71 }
michael@0 72 }
michael@0 73
michael@0 74 function getPendingDir() {
michael@0 75 let directoryService = Cc["@mozilla.org/file/directory_service;1"].
michael@0 76 getService(Ci.nsIProperties);
michael@0 77 let pendingDir = directoryService.get("UAppData", Ci.nsIFile);
michael@0 78 pendingDir.append("Crash Reports");
michael@0 79 pendingDir.append("pending");
michael@0 80 return pendingDir;
michael@0 81 }
michael@0 82
michael@0 83 function getPendingMinidump(id) {
michael@0 84 let pendingDir = getPendingDir();
michael@0 85 let dump = pendingDir.clone();
michael@0 86 let extra = pendingDir.clone();
michael@0 87 dump.append(id + ".dmp");
michael@0 88 extra.append(id + ".extra");
michael@0 89 return [dump, extra];
michael@0 90 }
michael@0 91
michael@0 92 function getAllPendingMinidumpsIDs() {
michael@0 93 let minidumps = [];
michael@0 94 let pendingDir = getPendingDir();
michael@0 95
michael@0 96 if (!(pendingDir.exists() && pendingDir.isDirectory()))
michael@0 97 return [];
michael@0 98 let entries = pendingDir.directoryEntries;
michael@0 99
michael@0 100 while (entries.hasMoreElements()) {
michael@0 101 let entry = entries.getNext().QueryInterface(Ci.nsIFile);
michael@0 102 if (entry.isFile()) {
michael@0 103 let matches = entry.leafName.match(/(.+)\.extra$/);
michael@0 104 if (matches)
michael@0 105 minidumps.push(matches[1]);
michael@0 106 }
michael@0 107 }
michael@0 108
michael@0 109 return minidumps;
michael@0 110 }
michael@0 111
michael@0 112 function pruneSavedDumps() {
michael@0 113 const KEEP = 10;
michael@0 114
michael@0 115 let pendingDir = getPendingDir();
michael@0 116 if (!(pendingDir.exists() && pendingDir.isDirectory()))
michael@0 117 return;
michael@0 118 let entries = pendingDir.directoryEntries;
michael@0 119 let entriesArray = [];
michael@0 120
michael@0 121 while (entries.hasMoreElements()) {
michael@0 122 let entry = entries.getNext().QueryInterface(Ci.nsIFile);
michael@0 123 if (entry.isFile()) {
michael@0 124 let matches = entry.leafName.match(/(.+)\.extra$/);
michael@0 125 if (matches)
michael@0 126 entriesArray.push(entry);
michael@0 127 }
michael@0 128 }
michael@0 129
michael@0 130 entriesArray.sort(function(a,b) {
michael@0 131 let dateA = a.lastModifiedTime;
michael@0 132 let dateB = b.lastModifiedTime;
michael@0 133 if (dateA < dateB)
michael@0 134 return -1;
michael@0 135 if (dateB < dateA)
michael@0 136 return 1;
michael@0 137 return 0;
michael@0 138 });
michael@0 139
michael@0 140 if (entriesArray.length > KEEP) {
michael@0 141 for (let i = 0; i < entriesArray.length - KEEP; ++i) {
michael@0 142 let extra = entriesArray[i];
michael@0 143 let matches = extra.leafName.match(/(.+)\.extra$/);
michael@0 144 if (matches) {
michael@0 145 let dump = extra.clone();
michael@0 146 dump.leafName = matches[1] + '.dmp';
michael@0 147 dump.remove(false);
michael@0 148 extra.remove(false);
michael@0 149 }
michael@0 150 }
michael@0 151 }
michael@0 152 }
michael@0 153
michael@0 154 function addFormEntry(doc, form, name, value) {
michael@0 155 var input = doc.createElement("input");
michael@0 156 input.type = "hidden";
michael@0 157 input.name = name;
michael@0 158 input.value = value;
michael@0 159 form.appendChild(input);
michael@0 160 }
michael@0 161
michael@0 162 function writeSubmittedReport(crashID, viewURL) {
michael@0 163 let directoryService = Cc["@mozilla.org/file/directory_service;1"].
michael@0 164 getService(Ci.nsIProperties);
michael@0 165 let reportFile = directoryService.get("UAppData", Ci.nsIFile);
michael@0 166 reportFile.append("Crash Reports");
michael@0 167 reportFile.append("submitted");
michael@0 168 if (!reportFile.exists())
michael@0 169 reportFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
michael@0 170 reportFile.append(crashID + ".txt");
michael@0 171 var fstream = Cc["@mozilla.org/network/file-output-stream;1"].
michael@0 172 createInstance(Ci.nsIFileOutputStream);
michael@0 173 // open, write, truncate
michael@0 174 fstream.init(reportFile, -1, -1, 0);
michael@0 175 var os = Cc["@mozilla.org/intl/converter-output-stream;1"].
michael@0 176 createInstance(Ci.nsIConverterOutputStream);
michael@0 177 os.init(fstream, "UTF-8", 0, 0x0000);
michael@0 178
michael@0 179 var data = strings.crashid.replace("%s", crashID);
michael@0 180 if (viewURL)
michael@0 181 data += "\n" + strings.reporturl.replace("%s", viewURL);
michael@0 182
michael@0 183 os.writeString(data);
michael@0 184 os.close();
michael@0 185 fstream.close();
michael@0 186 }
michael@0 187
michael@0 188 // the Submitter class represents an individual submission.
michael@0 189 function Submitter(id, submitSuccess, submitError, noThrottle,
michael@0 190 extraExtraKeyVals) {
michael@0 191 this.id = id;
michael@0 192 this.successCallback = submitSuccess;
michael@0 193 this.errorCallback = submitError;
michael@0 194 this.noThrottle = noThrottle;
michael@0 195 this.additionalDumps = [];
michael@0 196 this.extraKeyVals = extraExtraKeyVals || {};
michael@0 197 }
michael@0 198
michael@0 199 Submitter.prototype = {
michael@0 200 submitSuccess: function Submitter_submitSuccess(ret)
michael@0 201 {
michael@0 202 if (!ret.CrashID) {
michael@0 203 this.notifyStatus(FAILED);
michael@0 204 this.cleanup();
michael@0 205 return;
michael@0 206 }
michael@0 207
michael@0 208 // Write out the details file to submitted/
michael@0 209 writeSubmittedReport(ret.CrashID, ret.ViewURL);
michael@0 210
michael@0 211 // Delete from pending dir
michael@0 212 try {
michael@0 213 this.dump.remove(false);
michael@0 214 this.extra.remove(false);
michael@0 215 for (let i of this.additionalDumps) {
michael@0 216 i.dump.remove(false);
michael@0 217 }
michael@0 218 }
michael@0 219 catch (ex) {
michael@0 220 // report an error? not much the user can do here.
michael@0 221 }
michael@0 222
michael@0 223 this.notifyStatus(SUCCESS, ret);
michael@0 224 this.cleanup();
michael@0 225 },
michael@0 226
michael@0 227 cleanup: function Submitter_cleanup() {
michael@0 228 // drop some references just to be nice
michael@0 229 this.successCallback = null;
michael@0 230 this.errorCallback = null;
michael@0 231 this.iframe = null;
michael@0 232 this.dump = null;
michael@0 233 this.extra = null;
michael@0 234 this.additionalDumps = null;
michael@0 235 // remove this object from the list of active submissions
michael@0 236 let idx = CrashSubmit._activeSubmissions.indexOf(this);
michael@0 237 if (idx != -1)
michael@0 238 CrashSubmit._activeSubmissions.splice(idx, 1);
michael@0 239 },
michael@0 240
michael@0 241 submitForm: function Submitter_submitForm()
michael@0 242 {
michael@0 243 if (!('ServerURL' in this.extraKeyVals)) {
michael@0 244 return false;
michael@0 245 }
michael@0 246 let serverURL = this.extraKeyVals.ServerURL;
michael@0 247
michael@0 248 // Override the submission URL from the environment or prefs.
michael@0 249
michael@0 250 var envOverride = Cc['@mozilla.org/process/environment;1'].
michael@0 251 getService(Ci.nsIEnvironment).get("MOZ_CRASHREPORTER_URL");
michael@0 252 if (envOverride != '') {
michael@0 253 serverURL = envOverride;
michael@0 254 }
michael@0 255 else if ('PluginHang' in this.extraKeyVals) {
michael@0 256 try {
michael@0 257 serverURL = Services.prefs.
michael@0 258 getCharPref("toolkit.crashreporter.pluginHangSubmitURL");
michael@0 259 } catch(e) { }
michael@0 260 }
michael@0 261
michael@0 262 let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
michael@0 263 .createInstance(Ci.nsIXMLHttpRequest);
michael@0 264 xhr.open("POST", serverURL, true);
michael@0 265
michael@0 266 let formData = Cc["@mozilla.org/files/formdata;1"]
michael@0 267 .createInstance(Ci.nsIDOMFormData);
michael@0 268 // add the data
michael@0 269 for (let [name, value] in Iterator(this.extraKeyVals)) {
michael@0 270 if (name != "ServerURL") {
michael@0 271 formData.append(name, value);
michael@0 272 }
michael@0 273 }
michael@0 274 if (this.noThrottle) {
michael@0 275 // tell the server not to throttle this, since it was manually submitted
michael@0 276 formData.append("Throttleable", "0");
michael@0 277 }
michael@0 278 // add the minidumps
michael@0 279 formData.append("upload_file_minidump", File(this.dump.path));
michael@0 280 if (this.additionalDumps.length > 0) {
michael@0 281 let names = [];
michael@0 282 for (let i of this.additionalDumps) {
michael@0 283 names.push(i.name);
michael@0 284 formData.append("upload_file_minidump_"+i.name,
michael@0 285 File(i.dump.path));
michael@0 286 }
michael@0 287 }
michael@0 288
michael@0 289 let self = this;
michael@0 290 xhr.addEventListener("readystatechange", function (aEvt) {
michael@0 291 if (xhr.readyState == 4) {
michael@0 292 if (xhr.status != 200) {
michael@0 293 self.notifyStatus(FAILED);
michael@0 294 self.cleanup();
michael@0 295 } else {
michael@0 296 let ret = parseKeyValuePairs(xhr.responseText);
michael@0 297 self.submitSuccess(ret);
michael@0 298 }
michael@0 299 }
michael@0 300 }, false);
michael@0 301
michael@0 302 xhr.send(formData);
michael@0 303 return true;
michael@0 304 },
michael@0 305
michael@0 306 notifyStatus: function Submitter_notify(status, ret)
michael@0 307 {
michael@0 308 let propBag = Cc["@mozilla.org/hash-property-bag;1"].
michael@0 309 createInstance(Ci.nsIWritablePropertyBag2);
michael@0 310 propBag.setPropertyAsAString("minidumpID", this.id);
michael@0 311 if (status == SUCCESS) {
michael@0 312 propBag.setPropertyAsAString("serverCrashID", ret.CrashID);
michael@0 313 }
michael@0 314
michael@0 315 let extraKeyValsBag = Cc["@mozilla.org/hash-property-bag;1"].
michael@0 316 createInstance(Ci.nsIWritablePropertyBag2);
michael@0 317 for (let key in this.extraKeyVals) {
michael@0 318 extraKeyValsBag.setPropertyAsAString(key, this.extraKeyVals[key]);
michael@0 319 }
michael@0 320 propBag.setPropertyAsInterface("extra", extraKeyValsBag);
michael@0 321
michael@0 322 Services.obs.notifyObservers(propBag, "crash-report-status", status);
michael@0 323
michael@0 324 switch (status) {
michael@0 325 case SUCCESS:
michael@0 326 if (this.successCallback)
michael@0 327 this.successCallback(this.id, ret);
michael@0 328 break;
michael@0 329 case FAILED:
michael@0 330 if (this.errorCallback)
michael@0 331 this.errorCallback(this.id);
michael@0 332 break;
michael@0 333 default:
michael@0 334 // no callbacks invoked.
michael@0 335 }
michael@0 336 },
michael@0 337
michael@0 338 submit: function Submitter_submit()
michael@0 339 {
michael@0 340 let [dump, extra] = getPendingMinidump(this.id);
michael@0 341 if (!dump.exists() || !extra.exists()) {
michael@0 342 this.notifyStatus(FAILED);
michael@0 343 this.cleanup();
michael@0 344 return false;
michael@0 345 }
michael@0 346
michael@0 347 let extraKeyVals = parseKeyValuePairsFromFile(extra);
michael@0 348 for (let key in extraKeyVals) {
michael@0 349 if (!(key in this.extraKeyVals)) {
michael@0 350 this.extraKeyVals[key] = extraKeyVals[key];
michael@0 351 }
michael@0 352 }
michael@0 353
michael@0 354 let additionalDumps = [];
michael@0 355 if ("additional_minidumps" in this.extraKeyVals) {
michael@0 356 let names = this.extraKeyVals.additional_minidumps.split(',');
michael@0 357 for (let name of names) {
michael@0 358 let [dump, extra] = getPendingMinidump(this.id + "-" + name);
michael@0 359 if (!dump.exists()) {
michael@0 360 this.notifyStatus(FAILED);
michael@0 361 this.cleanup();
michael@0 362 return false;
michael@0 363 }
michael@0 364 additionalDumps.push({'name': name, 'dump': dump});
michael@0 365 }
michael@0 366 }
michael@0 367
michael@0 368 this.notifyStatus(SUBMITTING);
michael@0 369
michael@0 370 this.dump = dump;
michael@0 371 this.extra = extra;
michael@0 372 this.additionalDumps = additionalDumps;
michael@0 373
michael@0 374 if (!this.submitForm()) {
michael@0 375 this.notifyStatus(FAILED);
michael@0 376 this.cleanup();
michael@0 377 return false;
michael@0 378 }
michael@0 379 return true;
michael@0 380 }
michael@0 381 };
michael@0 382
michael@0 383 //===================================
michael@0 384 // External API goes here
michael@0 385 this.CrashSubmit = {
michael@0 386 /**
michael@0 387 * Submit the crash report named id.dmp from the "pending" directory.
michael@0 388 *
michael@0 389 * @param id
michael@0 390 * Filename (minus .dmp extension) of the minidump to submit.
michael@0 391 * @param params
michael@0 392 * An object containing any of the following optional parameters:
michael@0 393 * - submitSuccess
michael@0 394 * A function that will be called if the report is submitted
michael@0 395 * successfully with two parameters: the id that was passed
michael@0 396 * to this function, and an object containing the key/value
michael@0 397 * data returned from the server in its properties.
michael@0 398 * - submitError
michael@0 399 * A function that will be called with one parameter if the
michael@0 400 * report fails to submit: the id that was passed to this
michael@0 401 * function.
michael@0 402 * - noThrottle
michael@0 403 * If true, this crash report should be submitted with
michael@0 404 * an extra parameter of "Throttleable=0" indicating that
michael@0 405 * it should be processed right away. This should be set
michael@0 406 * when the report is being submitted and the user expects
michael@0 407 * to see the results immediately. Defaults to false.
michael@0 408 * - extraExtraKeyVals
michael@0 409 * An object whose key-value pairs will be merged with the data from
michael@0 410 * the ".extra" file submitted with the report. The properties of
michael@0 411 * this object will override properties of the same name in the
michael@0 412 * .extra file.
michael@0 413 *
michael@0 414 * @return true if the submission began successfully, or false if
michael@0 415 * it failed for some reason. (If the dump file does not
michael@0 416 * exist, for example.)
michael@0 417 */
michael@0 418 submit: function CrashSubmit_submit(id, params)
michael@0 419 {
michael@0 420 params = params || {};
michael@0 421 let submitSuccess = null;
michael@0 422 let submitError = null;
michael@0 423 let noThrottle = false;
michael@0 424 let extraExtraKeyVals = null;
michael@0 425
michael@0 426 if ('submitSuccess' in params)
michael@0 427 submitSuccess = params.submitSuccess;
michael@0 428 if ('submitError' in params)
michael@0 429 submitError = params.submitError;
michael@0 430 if ('noThrottle' in params)
michael@0 431 noThrottle = params.noThrottle;
michael@0 432 if ('extraExtraKeyVals' in params)
michael@0 433 extraExtraKeyVals = params.extraExtraKeyVals;
michael@0 434
michael@0 435 let submitter = new Submitter(id,
michael@0 436 submitSuccess,
michael@0 437 submitError,
michael@0 438 noThrottle,
michael@0 439 extraExtraKeyVals);
michael@0 440 CrashSubmit._activeSubmissions.push(submitter);
michael@0 441 return submitter.submit();
michael@0 442 },
michael@0 443
michael@0 444 /**
michael@0 445 * Delete the minidup from the "pending" directory.
michael@0 446 *
michael@0 447 * @param id
michael@0 448 * Filename (minus .dmp extension) of the minidump to delete.
michael@0 449 */
michael@0 450 delete: function CrashSubmit_delete(id) {
michael@0 451 let [dump, extra] = getPendingMinidump(id);
michael@0 452 dump.QueryInterface(Ci.nsIFile).remove(false);
michael@0 453 extra.QueryInterface(Ci.nsIFile).remove(false);
michael@0 454 },
michael@0 455
michael@0 456 /**
michael@0 457 * Get the list of pending crash IDs.
michael@0 458 *
michael@0 459 * @return an array of string, each being an ID as
michael@0 460 * expected to be passed to submit()
michael@0 461 */
michael@0 462 pendingIDs: function CrashSubmit_pendingIDs() {
michael@0 463 return getAllPendingMinidumpsIDs();
michael@0 464 },
michael@0 465
michael@0 466 /**
michael@0 467 * Prune the saved dumps.
michael@0 468 */
michael@0 469 pruneSavedDumps: function CrashSubmit_pruneSavedDumps() {
michael@0 470 pruneSavedDumps();
michael@0 471 },
michael@0 472
michael@0 473 // List of currently active submit objects
michael@0 474 _activeSubmissions: []
michael@0 475 };
michael@0 476
michael@0 477 // Run this when first loaded
michael@0 478 getL10nStrings();

mercurial