Sat, 03 Jan 2015 20:18:00 +0100
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 |