toolkit/components/url-classifier/content/moz/debug.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.

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 #ifdef DEBUG
michael@0 6
michael@0 7 // Generic logging/debugging functionality that:
michael@0 8 //
michael@0 9 // (*) when disabled compiles to no-ops at worst (for calls to the service)
michael@0 10 // and to nothing at best (calls to G_Debug() and similar are compiled
michael@0 11 // away when you use a jscompiler that strips dead code)
michael@0 12 //
michael@0 13 // (*) has dynamically configurable/creatable debugging "zones" enabling
michael@0 14 // selective logging
michael@0 15 //
michael@0 16 // (*) hides its plumbing so that all calls in different zones are uniform,
michael@0 17 // so you can drop files using this library into other apps that use it
michael@0 18 // without any configuration
michael@0 19 //
michael@0 20 // (*) can be controlled programmatically or via preferences. The
michael@0 21 // preferences that control the service and its zones are under
michael@0 22 // the preference branch "safebrowsing-debug-service."
michael@0 23 //
michael@0 24 // (*) outputs function call traces when the "loggifier" zone is enabled
michael@0 25 //
michael@0 26 // (*) can write output to logfiles so that you can get a call trace
michael@0 27 // from someone who is having a problem
michael@0 28 //
michael@0 29 // Example:
michael@0 30 //
michael@0 31 // var G_GDEBUG = true // Enable this module
michael@0 32 // var G_debugService = new G_DebugService(); // in global context
michael@0 33 //
michael@0 34 // // You can use it with arbitrary primitive first arguement
michael@0 35 // G_Debug("myzone", "Yo yo yo"); // outputs: [myzone] Yo yo yo\n
michael@0 36 //
michael@0 37 // // But it's nice to use it with an object; it will probe for the zone name
michael@0 38 // function Obj() {
michael@0 39 // this.debugZone = "someobj";
michael@0 40 // }
michael@0 41 // Obj.prototype.foo = function() {
michael@0 42 // G_Debug(this, "foo called");
michael@0 43 // }
michael@0 44 // (new Obj).foo(); // outputs: [someobj] foo called\n
michael@0 45 //
michael@0 46 // G_debugService.loggifier.loggify(Obj.prototype); // enable call tracing
michael@0 47 //
michael@0 48 // // En/disable specific zones programmatically (you can also use preferences)
michael@0 49 // G_debugService.enableZone("somezone");
michael@0 50 // G_debugService.disableZone("someotherzone");
michael@0 51 // G_debugService.enableAllZones();
michael@0 52 //
michael@0 53 // // We also have asserts and errors:
michael@0 54 // G_Error(this, "Some error occurred"); // will throw
michael@0 55 // G_Assert(this, (x > 3), "x not greater than three!"); // will throw
michael@0 56 //
michael@0 57 // See classes below for more methods.
michael@0 58 //
michael@0 59 // TODO add code to set prefs when not found to the default value of a tristate
michael@0 60 // TODO add error level support
michael@0 61 // TODO add ability to turn off console output
michael@0 62 //
michael@0 63 // -------> TO START DEBUGGING: set G_GDEBUG to true
michael@0 64
michael@0 65 // These are the functions code will typically call. Everything is
michael@0 66 // wrapped in if's so we can compile it away when G_GDEBUG is false.
michael@0 67
michael@0 68
michael@0 69 if (typeof G_GDEBUG == "undefined") {
michael@0 70 throw new Error("G_GDEBUG constant must be set before loading debug.js");
michael@0 71 }
michael@0 72
michael@0 73
michael@0 74 /**
michael@0 75 * Write out a debugging message.
michael@0 76 *
michael@0 77 * @param who The thingy to convert into a zone name corresponding to the
michael@0 78 * zone to which this message belongs
michael@0 79 * @param msg Message to output
michael@0 80 */
michael@0 81 function G_Debug(who, msg) {
michael@0 82 if (G_GDEBUG) {
michael@0 83 G_GetDebugZone(who).debug(msg);
michael@0 84 }
michael@0 85 }
michael@0 86
michael@0 87 /**
michael@0 88 * Debugs loudly
michael@0 89 */
michael@0 90 function G_DebugL(who, msg) {
michael@0 91 if (G_GDEBUG) {
michael@0 92 var zone = G_GetDebugZone(who);
michael@0 93
michael@0 94 if (zone.zoneIsEnabled()) {
michael@0 95 G_debugService.dump(
michael@0 96 "\n************************************************************\n");
michael@0 97
michael@0 98 G_Debug(who, msg);
michael@0 99
michael@0 100 G_debugService.dump(
michael@0 101 "************************************************************\n\n");
michael@0 102 }
michael@0 103 }
michael@0 104 }
michael@0 105
michael@0 106 /**
michael@0 107 * Write out a call tracing message
michael@0 108 *
michael@0 109 * @param who The thingy to convert into a zone name corresponding to the
michael@0 110 * zone to which this message belongs
michael@0 111 * @param msg Message to output
michael@0 112 */
michael@0 113 function G_TraceCall(who, msg) {
michael@0 114 if (G_GDEBUG) {
michael@0 115 if (G_debugService.callTracingEnabled()) {
michael@0 116 G_debugService.dump(msg + "\n");
michael@0 117 }
michael@0 118 }
michael@0 119 }
michael@0 120
michael@0 121 /**
michael@0 122 * Write out an error (and throw)
michael@0 123 *
michael@0 124 * @param who The thingy to convert into a zone name corresponding to the
michael@0 125 * zone to which this message belongs
michael@0 126 * @param msg Message to output
michael@0 127 */
michael@0 128 function G_Error(who, msg) {
michael@0 129 if (G_GDEBUG) {
michael@0 130 G_GetDebugZone(who).error(msg);
michael@0 131 }
michael@0 132 }
michael@0 133
michael@0 134 /**
michael@0 135 * Assert something as true and signal an error if it's not
michael@0 136 *
michael@0 137 * @param who The thingy to convert into a zone name corresponding to the
michael@0 138 * zone to which this message belongs
michael@0 139 * @param condition Boolean condition to test
michael@0 140 * @param msg Message to output
michael@0 141 */
michael@0 142 function G_Assert(who, condition, msg) {
michael@0 143 if (G_GDEBUG) {
michael@0 144 G_GetDebugZone(who).assert(condition, msg);
michael@0 145 }
michael@0 146 }
michael@0 147
michael@0 148 /**
michael@0 149 * Helper function that takes input and returns the DebugZone
michael@0 150 * corresponding to it.
michael@0 151 *
michael@0 152 * @param who Arbitrary input that will be converted into a zone name. Most
michael@0 153 * likely an object that has .debugZone property, or a string.
michael@0 154 * @returns The DebugZone object corresponding to the input
michael@0 155 */
michael@0 156 function G_GetDebugZone(who) {
michael@0 157 if (G_GDEBUG) {
michael@0 158 var zone = "?";
michael@0 159
michael@0 160 if (who && who.debugZone) {
michael@0 161 zone = who.debugZone;
michael@0 162 } else if (typeof who == "string") {
michael@0 163 zone = who;
michael@0 164 }
michael@0 165
michael@0 166 return G_debugService.getZone(zone);
michael@0 167 }
michael@0 168 }
michael@0 169
michael@0 170 // Classes that implement the functionality.
michael@0 171
michael@0 172 /**
michael@0 173 * A debug "zone" is a string derived from arbitrary types (but
michael@0 174 * typically derived from another string or an object). All debugging
michael@0 175 * messages using a particular zone can be enabled or disabled
michael@0 176 * independent of other zones. This enables you to turn on/off logging
michael@0 177 * of particular objects or modules. This object implements a single
michael@0 178 * zone and the methods required to use it.
michael@0 179 *
michael@0 180 * @constructor
michael@0 181 * @param service Reference to the DebugService object we use for
michael@0 182 * registration
michael@0 183 * @param prefix String indicating the unique prefix we should use
michael@0 184 * when creating preferences to control this zone
michael@0 185 * @param zone String indicating the name of the zone
michael@0 186 */
michael@0 187 function G_DebugZone(service, prefix, zone) {
michael@0 188 if (G_GDEBUG) {
michael@0 189 this.debugService_ = service;
michael@0 190 this.prefix_ = prefix;
michael@0 191 this.zone_ = zone;
michael@0 192 this.zoneEnabledPrefName_ = prefix + ".zone." + this.zone_;
michael@0 193 this.settings_ = new G_DebugSettings();
michael@0 194 }
michael@0 195 }
michael@0 196
michael@0 197 /**
michael@0 198 * @returns Boolean indicating if this zone is enabled
michael@0 199 */
michael@0 200 G_DebugZone.prototype.zoneIsEnabled = function() {
michael@0 201 if (G_GDEBUG) {
michael@0 202 var explicit = this.settings_.getSetting(this.zoneEnabledPrefName_, null);
michael@0 203
michael@0 204 if (explicit !== null) {
michael@0 205 return explicit;
michael@0 206 } else {
michael@0 207 return this.debugService_.allZonesEnabled();
michael@0 208 }
michael@0 209 }
michael@0 210 }
michael@0 211
michael@0 212 /**
michael@0 213 * Enable this logging zone
michael@0 214 */
michael@0 215 G_DebugZone.prototype.enableZone = function() {
michael@0 216 if (G_GDEBUG) {
michael@0 217 this.settings_.setDefault(this.zoneEnabledPrefName_, true);
michael@0 218 }
michael@0 219 }
michael@0 220
michael@0 221 /**
michael@0 222 * Disable this logging zone
michael@0 223 */
michael@0 224 G_DebugZone.prototype.disableZone = function() {
michael@0 225 if (G_GDEBUG) {
michael@0 226 this.settings_.setDefault(this.zoneEnabledPrefName_, false);
michael@0 227 }
michael@0 228 }
michael@0 229
michael@0 230 /**
michael@0 231 * Write a debugging message to this zone
michael@0 232 *
michael@0 233 * @param msg String of message to write
michael@0 234 */
michael@0 235 G_DebugZone.prototype.debug = function(msg) {
michael@0 236 if (G_GDEBUG) {
michael@0 237 if (this.zoneIsEnabled()) {
michael@0 238 this.debugService_.dump("[" + this.zone_ + "] " + msg + "\n");
michael@0 239 }
michael@0 240 }
michael@0 241 }
michael@0 242
michael@0 243 /**
michael@0 244 * Write an error to this zone and throw
michael@0 245 *
michael@0 246 * @param msg String of error to write
michael@0 247 */
michael@0 248 G_DebugZone.prototype.error = function(msg) {
michael@0 249 if (G_GDEBUG) {
michael@0 250 this.debugService_.dump("[" + this.zone_ + "] " + msg + "\n");
michael@0 251 throw new Error(msg);
michael@0 252 debugger;
michael@0 253 }
michael@0 254 }
michael@0 255
michael@0 256 /**
michael@0 257 * Assert something as true and error if it is not
michael@0 258 *
michael@0 259 * @param condition Boolean condition to test
michael@0 260 * @param msg String of message to write if is false
michael@0 261 */
michael@0 262 G_DebugZone.prototype.assert = function(condition, msg) {
michael@0 263 if (G_GDEBUG) {
michael@0 264 if (condition !== true) {
michael@0 265 G_Error(this.zone_, "ASSERT FAILED: " + msg);
michael@0 266 }
michael@0 267 }
michael@0 268 }
michael@0 269
michael@0 270
michael@0 271 /**
michael@0 272 * The debug service handles auto-registration of zones, namespacing
michael@0 273 * the zones preferences, and various global settings such as whether
michael@0 274 * all zones are enabled.
michael@0 275 *
michael@0 276 * @constructor
michael@0 277 * @param opt_prefix Optional string indicating the unique prefix we should
michael@0 278 * use when creating preferences
michael@0 279 */
michael@0 280 function G_DebugService(opt_prefix) {
michael@0 281 if (G_GDEBUG) {
michael@0 282 this.prefix_ = opt_prefix ? opt_prefix : "safebrowsing-debug-service";
michael@0 283 this.consoleEnabledPrefName_ = this.prefix_ + ".alsologtoconsole";
michael@0 284 this.allZonesEnabledPrefName_ = this.prefix_ + ".enableallzones";
michael@0 285 this.callTracingEnabledPrefName_ = this.prefix_ + ".trace-function-calls";
michael@0 286 this.logFileEnabledPrefName_ = this.prefix_ + ".logfileenabled";
michael@0 287 this.logFileErrorLevelPrefName_ = this.prefix_ + ".logfile-errorlevel";
michael@0 288 this.zones_ = {};
michael@0 289
michael@0 290 this.loggifier = new G_Loggifier();
michael@0 291 this.settings_ = new G_DebugSettings();
michael@0 292 }
michael@0 293 }
michael@0 294
michael@0 295 // Error levels for reporting console messages to the log.
michael@0 296 G_DebugService.ERROR_LEVEL_INFO = "INFO";
michael@0 297 G_DebugService.ERROR_LEVEL_WARNING = "WARNING";
michael@0 298 G_DebugService.ERROR_LEVEL_EXCEPTION = "EXCEPTION";
michael@0 299
michael@0 300
michael@0 301 /**
michael@0 302 * @returns Boolean indicating if we should send messages to the jsconsole
michael@0 303 */
michael@0 304 G_DebugService.prototype.alsoDumpToConsole = function() {
michael@0 305 if (G_GDEBUG) {
michael@0 306 return this.settings_.getSetting(this.consoleEnabledPrefName_, false);
michael@0 307 }
michael@0 308 }
michael@0 309
michael@0 310 /**
michael@0 311 * @returns whether to log output to a file as well as the console.
michael@0 312 */
michael@0 313 G_DebugService.prototype.logFileIsEnabled = function() {
michael@0 314 if (G_GDEBUG) {
michael@0 315 return this.settings_.getSetting(this.logFileEnabledPrefName_, false);
michael@0 316 }
michael@0 317 }
michael@0 318
michael@0 319 /**
michael@0 320 * Turns on file logging. dump() output will also go to the file specified by
michael@0 321 * setLogFile()
michael@0 322 */
michael@0 323 G_DebugService.prototype.enableLogFile = function() {
michael@0 324 if (G_GDEBUG) {
michael@0 325 this.settings_.setDefault(this.logFileEnabledPrefName_, true);
michael@0 326 }
michael@0 327 }
michael@0 328
michael@0 329 /**
michael@0 330 * Turns off file logging
michael@0 331 */
michael@0 332 G_DebugService.prototype.disableLogFile = function() {
michael@0 333 if (G_GDEBUG) {
michael@0 334 this.settings_.setDefault(this.logFileEnabledPrefName_, false);
michael@0 335 }
michael@0 336 }
michael@0 337
michael@0 338 /**
michael@0 339 * @returns an nsIFile instance pointing to the current log file location
michael@0 340 */
michael@0 341 G_DebugService.prototype.getLogFile = function() {
michael@0 342 if (G_GDEBUG) {
michael@0 343 return this.logFile_;
michael@0 344 }
michael@0 345 }
michael@0 346
michael@0 347 /**
michael@0 348 * Sets a new log file location
michael@0 349 */
michael@0 350 G_DebugService.prototype.setLogFile = function(file) {
michael@0 351 if (G_GDEBUG) {
michael@0 352 this.logFile_ = file;
michael@0 353 }
michael@0 354 }
michael@0 355
michael@0 356 /**
michael@0 357 * Enables sending messages to the jsconsole
michael@0 358 */
michael@0 359 G_DebugService.prototype.enableDumpToConsole = function() {
michael@0 360 if (G_GDEBUG) {
michael@0 361 this.settings_.setDefault(this.consoleEnabledPrefName_, true);
michael@0 362 }
michael@0 363 }
michael@0 364
michael@0 365 /**
michael@0 366 * Disables sending messages to the jsconsole
michael@0 367 */
michael@0 368 G_DebugService.prototype.disableDumpToConsole = function() {
michael@0 369 if (G_GDEBUG) {
michael@0 370 this.settings_.setDefault(this.consoleEnabledPrefName_, false);
michael@0 371 }
michael@0 372 }
michael@0 373
michael@0 374 /**
michael@0 375 * @param zone Name of the zone to get
michael@0 376 * @returns The DebugZone object corresopnding to input. If not such
michael@0 377 * zone exists, a new one is created and returned
michael@0 378 */
michael@0 379 G_DebugService.prototype.getZone = function(zone) {
michael@0 380 if (G_GDEBUG) {
michael@0 381 if (!this.zones_[zone])
michael@0 382 this.zones_[zone] = new G_DebugZone(this, this.prefix_, zone);
michael@0 383
michael@0 384 return this.zones_[zone];
michael@0 385 }
michael@0 386 }
michael@0 387
michael@0 388 /**
michael@0 389 * @param zone Zone to enable debugging for
michael@0 390 */
michael@0 391 G_DebugService.prototype.enableZone = function(zone) {
michael@0 392 if (G_GDEBUG) {
michael@0 393 var toEnable = this.getZone(zone);
michael@0 394 toEnable.enableZone();
michael@0 395 }
michael@0 396 }
michael@0 397
michael@0 398 /**
michael@0 399 * @param zone Zone to disable debugging for
michael@0 400 */
michael@0 401 G_DebugService.prototype.disableZone = function(zone) {
michael@0 402 if (G_GDEBUG) {
michael@0 403 var toDisable = this.getZone(zone);
michael@0 404 toDisable.disableZone();
michael@0 405 }
michael@0 406 }
michael@0 407
michael@0 408 /**
michael@0 409 * @returns Boolean indicating whether debugging is enabled for all zones
michael@0 410 */
michael@0 411 G_DebugService.prototype.allZonesEnabled = function() {
michael@0 412 if (G_GDEBUG) {
michael@0 413 return this.settings_.getSetting(this.allZonesEnabledPrefName_, false);
michael@0 414 }
michael@0 415 }
michael@0 416
michael@0 417 /**
michael@0 418 * Enables all debugging zones
michael@0 419 */
michael@0 420 G_DebugService.prototype.enableAllZones = function() {
michael@0 421 if (G_GDEBUG) {
michael@0 422 this.settings_.setDefault(this.allZonesEnabledPrefName_, true);
michael@0 423 }
michael@0 424 }
michael@0 425
michael@0 426 /**
michael@0 427 * Disables all debugging zones
michael@0 428 */
michael@0 429 G_DebugService.prototype.disableAllZones = function() {
michael@0 430 if (G_GDEBUG) {
michael@0 431 this.settings_.setDefault(this.allZonesEnabledPrefName_, false);
michael@0 432 }
michael@0 433 }
michael@0 434
michael@0 435 /**
michael@0 436 * @returns Boolean indicating whether call tracing is enabled
michael@0 437 */
michael@0 438 G_DebugService.prototype.callTracingEnabled = function() {
michael@0 439 if (G_GDEBUG) {
michael@0 440 return this.settings_.getSetting(this.callTracingEnabledPrefName_, false);
michael@0 441 }
michael@0 442 }
michael@0 443
michael@0 444 /**
michael@0 445 * Enables call tracing
michael@0 446 */
michael@0 447 G_DebugService.prototype.enableCallTracing = function() {
michael@0 448 if (G_GDEBUG) {
michael@0 449 this.settings_.setDefault(this.callTracingEnabledPrefName_, true);
michael@0 450 }
michael@0 451 }
michael@0 452
michael@0 453 /**
michael@0 454 * Disables call tracing
michael@0 455 */
michael@0 456 G_DebugService.prototype.disableCallTracing = function() {
michael@0 457 if (G_GDEBUG) {
michael@0 458 this.settings_.setDefault(this.callTracingEnabledPrefName_, false);
michael@0 459 }
michael@0 460 }
michael@0 461
michael@0 462 /**
michael@0 463 * Gets the minimum error that will be reported to the log.
michael@0 464 */
michael@0 465 G_DebugService.prototype.getLogFileErrorLevel = function() {
michael@0 466 if (G_GDEBUG) {
michael@0 467 var level = this.settings_.getSetting(this.logFileErrorLevelPrefName_,
michael@0 468 G_DebugService.ERROR_LEVEL_EXCEPTION);
michael@0 469
michael@0 470 return level.toUpperCase();
michael@0 471 }
michael@0 472 }
michael@0 473
michael@0 474 /**
michael@0 475 * Sets the minimum error level that will be reported to the log.
michael@0 476 */
michael@0 477 G_DebugService.prototype.setLogFileErrorLevel = function(level) {
michael@0 478 if (G_GDEBUG) {
michael@0 479 // normalize case just to make it slightly easier to not screw up.
michael@0 480 level = level.toUpperCase();
michael@0 481
michael@0 482 if (level != G_DebugService.ERROR_LEVEL_INFO &&
michael@0 483 level != G_DebugService.ERROR_LEVEL_WARNING &&
michael@0 484 level != G_DebugService.ERROR_LEVEL_EXCEPTION) {
michael@0 485 throw new Error("Invalid error level specified: {" + level + "}");
michael@0 486 }
michael@0 487
michael@0 488 this.settings_.setDefault(this.logFileErrorLevelPrefName_, level);
michael@0 489 }
michael@0 490 }
michael@0 491
michael@0 492 /**
michael@0 493 * Internal dump() method
michael@0 494 *
michael@0 495 * @param msg String of message to dump
michael@0 496 */
michael@0 497 G_DebugService.prototype.dump = function(msg) {
michael@0 498 if (G_GDEBUG) {
michael@0 499 dump(msg);
michael@0 500
michael@0 501 if (this.alsoDumpToConsole()) {
michael@0 502 try {
michael@0 503 var console = Components.classes['@mozilla.org/consoleservice;1']
michael@0 504 .getService(Components.interfaces.nsIConsoleService);
michael@0 505 console.logStringMessage(msg);
michael@0 506 } catch(e) {
michael@0 507 dump("G_DebugZone ERROR: COULD NOT DUMP TO CONSOLE\n");
michael@0 508 }
michael@0 509 }
michael@0 510
michael@0 511 this.maybeDumpToFile(msg);
michael@0 512 }
michael@0 513 }
michael@0 514
michael@0 515 /**
michael@0 516 * Writes the specified message to the log file, if file logging is enabled.
michael@0 517 */
michael@0 518 G_DebugService.prototype.maybeDumpToFile = function(msg) {
michael@0 519 if (this.logFileIsEnabled() && this.logFile_) {
michael@0 520
michael@0 521 /* try to get the correct line end character for this platform */
michael@0 522 if (!this._LINE_END_CHAR)
michael@0 523 this._LINE_END_CHAR =
michael@0 524 Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
michael@0 525 .OS == "WINNT" ? "\r\n" : "\n";
michael@0 526 if (this._LINE_END_CHAR != "\n")
michael@0 527 msg = msg.replace(/\n/g, this._LINE_END_CHAR);
michael@0 528
michael@0 529 try {
michael@0 530 var stream = Cc["@mozilla.org/network/file-output-stream;1"]
michael@0 531 .createInstance(Ci.nsIFileOutputStream);
michael@0 532 stream.init(this.logFile_,
michael@0 533 0x02 | 0x08 | 0x10 /* PR_WRONLY | PR_CREATE_FILE | PR_APPEND */
michael@0 534 -1 /* default perms */, 0 /* no special behavior */);
michael@0 535 stream.write(msg, msg.length);
michael@0 536 } finally {
michael@0 537 stream.close();
michael@0 538 }
michael@0 539 }
michael@0 540 }
michael@0 541
michael@0 542 /**
michael@0 543 * Implements nsIConsoleListener.observe(). Gets called when an error message
michael@0 544 * gets reported to the console and sends it to the log file as well.
michael@0 545 */
michael@0 546 G_DebugService.prototype.observe = function(consoleMessage) {
michael@0 547 if (G_GDEBUG) {
michael@0 548 var errorLevel = this.getLogFileErrorLevel();
michael@0 549
michael@0 550 // consoleMessage can be either nsIScriptError or nsIConsoleMessage. The
michael@0 551 // latter does not have things like line number, etc. So we special case
michael@0 552 // it first.
michael@0 553 if (!(consoleMessage instanceof Ci.nsIScriptError)) {
michael@0 554 // Only report these messages if the error level is INFO.
michael@0 555 if (errorLevel == G_DebugService.ERROR_LEVEL_INFO) {
michael@0 556 this.maybeDumpToFile(G_DebugService.ERROR_LEVEL_INFO + ": " +
michael@0 557 consoleMessage.message + "\n");
michael@0 558 }
michael@0 559
michael@0 560 return;
michael@0 561 }
michael@0 562
michael@0 563 // We make a local copy of these fields because writing to it doesn't seem
michael@0 564 // to work.
michael@0 565 var flags = consoleMessage.flags;
michael@0 566 var sourceName = consoleMessage.sourceName;
michael@0 567 var lineNumber = consoleMessage.lineNumber;
michael@0 568
michael@0 569 // Sometimes, a scripterror instance won't have any flags set. We
michael@0 570 // default to exception.
michael@0 571 if (!flags) {
michael@0 572 flags = Ci.nsIScriptError.exceptionFlag;
michael@0 573 }
michael@0 574
michael@0 575 // Default the filename and line number if they aren't set.
michael@0 576 if (!sourceName) {
michael@0 577 sourceName = "<unknown>";
michael@0 578 }
michael@0 579
michael@0 580 if (!lineNumber) {
michael@0 581 lineNumber = "<unknown>";
michael@0 582 }
michael@0 583
michael@0 584 // Report the error in the log file.
michael@0 585 if (flags & Ci.nsIScriptError.warningFlag) {
michael@0 586 // Only report warnings if the error level is warning or better.
michael@0 587 if (errorLevel == G_DebugService.ERROR_LEVEL_WARNING ||
michael@0 588 errorLevel == G_DebugService.ERROR_LEVEL_INFO) {
michael@0 589 this.reportScriptError_(consoleMessage.message,
michael@0 590 sourceName,
michael@0 591 lineNumber,
michael@0 592 G_DebugService.ERROR_LEVEL_WARNING);
michael@0 593 }
michael@0 594 } else if (flags & Ci.nsIScriptError.exceptionFlag) {
michael@0 595 // Always report exceptions.
michael@0 596 this.reportScriptError_(consoleMessage.message,
michael@0 597 sourceName,
michael@0 598 lineNumber,
michael@0 599 G_DebugService.ERROR_LEVEL_EXCEPTION);
michael@0 600 }
michael@0 601 }
michael@0 602 }
michael@0 603
michael@0 604 /**
michael@0 605 * Private helper to report an nsIScriptError instance to the log/console.
michael@0 606 */
michael@0 607 G_DebugService.prototype.reportScriptError_ = function(message, sourceName,
michael@0 608 lineNumber, label) {
michael@0 609 message = "\n------------------------------------------------------------\n" +
michael@0 610 label + ": " + message +
michael@0 611 "\nlocation: " + sourceName + ", " + "line: " + lineNumber +
michael@0 612 "\n------------------------------------------------------------\n\n";
michael@0 613
michael@0 614 dump(message);
michael@0 615 this.maybeDumpToFile(message);
michael@0 616 }
michael@0 617
michael@0 618
michael@0 619
michael@0 620 /**
michael@0 621 * A class that instruments methods so they output a call trace,
michael@0 622 * including the values of their actual parameters and return value.
michael@0 623 * This code is mostly stolen from Aaron Boodman's original
michael@0 624 * implementation in clobber utils.
michael@0 625 *
michael@0 626 * Note that this class uses the "loggifier" debug zone, so you'll see
michael@0 627 * a complete call trace when that zone is enabled.
michael@0 628 *
michael@0 629 * @constructor
michael@0 630 */
michael@0 631 function G_Loggifier() {
michael@0 632 if (G_GDEBUG) {
michael@0 633 // Careful not to loggify ourselves!
michael@0 634 this.mark_(this);
michael@0 635 }
michael@0 636 }
michael@0 637
michael@0 638 /**
michael@0 639 * Marks an object as having been loggified. Loggification is not
michael@0 640 * idempotent :)
michael@0 641 *
michael@0 642 * @param obj Object to be marked
michael@0 643 */
michael@0 644 G_Loggifier.prototype.mark_ = function(obj) {
michael@0 645 if (G_GDEBUG) {
michael@0 646 obj.__loggified_ = true;
michael@0 647 }
michael@0 648 }
michael@0 649
michael@0 650 /**
michael@0 651 * @param obj Object to be examined
michael@0 652 * @returns Boolean indicating if the object has been loggified
michael@0 653 */
michael@0 654 G_Loggifier.prototype.isLoggified = function(obj) {
michael@0 655 if (G_GDEBUG) {
michael@0 656 return !!obj.__loggified_;
michael@0 657 }
michael@0 658 }
michael@0 659
michael@0 660 /**
michael@0 661 * Attempt to extract the class name from the constructor definition.
michael@0 662 * Assumes the object was created using new.
michael@0 663 *
michael@0 664 * @param constructor String containing the definition of a constructor,
michael@0 665 * for example what you'd get by examining obj.constructor
michael@0 666 * @returns Name of the constructor/object if it could be found, else "???"
michael@0 667 */
michael@0 668 G_Loggifier.prototype.getFunctionName_ = function(constructor) {
michael@0 669 if (G_GDEBUG) {
michael@0 670 return constructor.name || "???";
michael@0 671 }
michael@0 672 }
michael@0 673
michael@0 674 /**
michael@0 675 * Wraps all the methods in an object so that call traces are
michael@0 676 * automatically outputted.
michael@0 677 *
michael@0 678 * @param obj Object to loggify. SHOULD BE THE PROTOTYPE OF A USER-DEFINED
michael@0 679 * object. You can get into trouble if you attempt to
michael@0 680 * loggify something that isn't, for example the Window.
michael@0 681 *
michael@0 682 * Any additional parameters are considered method names which should not be
michael@0 683 * loggified.
michael@0 684 *
michael@0 685 * Usage:
michael@0 686 * G_debugService.loggifier.loggify(MyClass.prototype,
michael@0 687 * "firstMethodNotToLog",
michael@0 688 * "secondMethodNotToLog",
michael@0 689 * ... etc ...);
michael@0 690 */
michael@0 691 G_Loggifier.prototype.loggify = function(obj) {
michael@0 692 if (G_GDEBUG) {
michael@0 693 if (!G_debugService.callTracingEnabled()) {
michael@0 694 return;
michael@0 695 }
michael@0 696
michael@0 697 if (typeof window != "undefined" && obj == window ||
michael@0 698 this.isLoggified(obj)) // Don't go berserk!
michael@0 699 return;
michael@0 700
michael@0 701 var zone = G_GetDebugZone(obj);
michael@0 702 if (!zone || !zone.zoneIsEnabled()) {
michael@0 703 return;
michael@0 704 }
michael@0 705
michael@0 706 this.mark_(obj);
michael@0 707
michael@0 708 // Helper function returns an instrumented version of
michael@0 709 // objName.meth, with "this" bound properly. (BTW, because we're
michael@0 710 // in a conditional here, functions will only be defined as
michael@0 711 // they're encountered during execution, so declare this helper
michael@0 712 // before using it.)
michael@0 713
michael@0 714 function wrap(meth, objName, methName) {
michael@0 715 return function() {
michael@0 716
michael@0 717 // First output the call along with actual parameters
michael@0 718 var args = new Array(arguments.length);
michael@0 719 var argsString = "";
michael@0 720 for (var i = 0; i < args.length; i++) {
michael@0 721 args[i] = arguments[i];
michael@0 722 argsString += (i == 0 ? "" : ", ");
michael@0 723
michael@0 724 if (typeof args[i] == "function") {
michael@0 725 argsString += "[function]";
michael@0 726 } else {
michael@0 727 argsString += args[i];
michael@0 728 }
michael@0 729 }
michael@0 730
michael@0 731 G_TraceCall(this, "> " + objName + "." + methName + "(" +
michael@0 732 argsString + ")");
michael@0 733
michael@0 734 // Then run the function, capturing the return value and throws
michael@0 735 try {
michael@0 736 var retVal = meth.apply(this, arguments);
michael@0 737 var reportedRetVal = retVal;
michael@0 738
michael@0 739 if (typeof reportedRetVal == "undefined")
michael@0 740 reportedRetVal = "void";
michael@0 741 else if (reportedRetVal === "")
michael@0 742 reportedRetVal = "\"\" (empty string)";
michael@0 743 } catch (e) {
michael@0 744 if (e && !e.__logged) {
michael@0 745 G_TraceCall(this, "Error: " + e.message + ". " +
michael@0 746 e.fileName + ": " + e.lineNumber);
michael@0 747 try {
michael@0 748 e.__logged = true;
michael@0 749 } catch (e2) {
michael@0 750 // Sometimes we can't add the __logged flag because it's an
michael@0 751 // XPC wrapper
michael@0 752 throw e;
michael@0 753 }
michael@0 754 }
michael@0 755
michael@0 756 throw e; // Re-throw!
michael@0 757 }
michael@0 758
michael@0 759 // And spit it out already
michael@0 760 G_TraceCall(
michael@0 761 this,
michael@0 762 "< " + objName + "." + methName + ": " + reportedRetVal);
michael@0 763
michael@0 764 return retVal;
michael@0 765 };
michael@0 766 };
michael@0 767
michael@0 768 var ignoreLookup = {};
michael@0 769
michael@0 770 if (arguments.length > 1) {
michael@0 771 for (var i = 1; i < arguments.length; i++) {
michael@0 772 ignoreLookup[arguments[i]] = true;
michael@0 773 }
michael@0 774 }
michael@0 775
michael@0 776 // Wrap each method of obj
michael@0 777 for (var p in obj) {
michael@0 778 // Work around bug in Firefox. In ffox typeof RegExp is "function",
michael@0 779 // so make sure this really is a function. Bug as of FFox 1.5b2.
michael@0 780 if (typeof obj[p] == "function" && obj[p].call && !ignoreLookup[p]) {
michael@0 781 var objName = this.getFunctionName_(obj.constructor);
michael@0 782 obj[p] = wrap(obj[p], objName, p);
michael@0 783 }
michael@0 784 }
michael@0 785 }
michael@0 786 }
michael@0 787
michael@0 788
michael@0 789 /**
michael@0 790 * Simple abstraction around debug settings. The thing with debug settings is
michael@0 791 * that we want to be able to specify a default in the application's startup,
michael@0 792 * but have that default be overridable by the user via their prefs.
michael@0 793 *
michael@0 794 * To generalize this, we package up a dictionary of defaults with the
michael@0 795 * preferences tree. If a setting isn't in the preferences tree, then we grab it
michael@0 796 * from the defaults.
michael@0 797 */
michael@0 798 function G_DebugSettings() {
michael@0 799 this.defaults_ = {};
michael@0 800 this.prefs_ = new G_Preferences();
michael@0 801 }
michael@0 802
michael@0 803 /**
michael@0 804 * Returns the value of a settings, optionally defaulting to a given value if it
michael@0 805 * doesn't exist. If no default is specified, the default is |undefined|.
michael@0 806 */
michael@0 807 G_DebugSettings.prototype.getSetting = function(name, opt_default) {
michael@0 808 var override = this.prefs_.getPref(name, null);
michael@0 809
michael@0 810 if (override !== null) {
michael@0 811 return override;
michael@0 812 } else if (typeof this.defaults_[name] != "undefined") {
michael@0 813 return this.defaults_[name];
michael@0 814 } else {
michael@0 815 return opt_default;
michael@0 816 }
michael@0 817 }
michael@0 818
michael@0 819 /**
michael@0 820 * Sets the default value for a setting. If the user doesn't override it with a
michael@0 821 * preference, this is the value which will be returned by getSetting().
michael@0 822 */
michael@0 823 G_DebugSettings.prototype.setDefault = function(name, val) {
michael@0 824 this.defaults_[name] = val;
michael@0 825 }
michael@0 826
michael@0 827 var G_debugService = new G_DebugService(); // Instantiate us!
michael@0 828
michael@0 829 if (G_GDEBUG) {
michael@0 830 G_debugService.enableAllZones();
michael@0 831 }
michael@0 832
michael@0 833 #else
michael@0 834
michael@0 835 // Stubs for the debugging aids scattered through this component.
michael@0 836 // They will be expanded if you compile yourself a debug build.
michael@0 837
michael@0 838 function G_Debug(who, msg) { }
michael@0 839 function G_Assert(who, condition, msg) { }
michael@0 840 function G_Error(who, msg) { }
michael@0 841 var G_debugService = { __noSuchMethod__: function() { } };
michael@0 842
michael@0 843 #endif

mercurial