1 /* See license.txt for terms of usage */ 2 3 FBL.ns(function() { with (FBL) { 4 5 // ************************************************************************************************ 6 // Constants 7 8 const Cc = Components.classes; 9 const Ci = Components.interfaces; 10 const nsIScriptError = Ci.nsIScriptError; 11 const nsIConsoleMessage = Ci.nsIConsoleMessage; 12 13 const WARNING_FLAG = nsIScriptError.warningFlag; 14 15 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 16 17 const urlRe = new RegExp("([^:]*):(//)?([^/]*)"); 18 const reUncaught = /uncaught exception/; 19 const reException = /uncaught exception:\s\[Exception...\s\"([^\"]*)\".*nsresult:.*\(([^\)]*)\).*location:\s\"([^\s]*)\sLine:\s(\d*)\"/; 20 const statusBar = $("fbStatusBar"); 21 const statusText = $("fbStatusText"); 22 23 const pointlessErrors = 24 { 25 "uncaught exception: Permission denied to call method Location.toString": 1, 26 "uncaught exception: Permission denied to get property Window.writeDebug": 1, 27 "uncaught exception: Permission denied to get property XULElement.accessKey": 1, 28 "this.docShell has no properties": 1, 29 "aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation).currentURI has no properties": 1, 30 "Deprecated property window.title used. Please use document.title instead.": 1, 31 "Key event not available on GTK2:": 1 32 }; 33 34 35 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 36 37 const fbs = Cc["@joehewitt.com/firebug;1"].getService().wrappedJSObject; 38 const consoleService = CCSV("@mozilla.org/consoleservice;1", "nsIConsoleService"); 39 40 // ************************************************************************************************ 41 42 var Errors = Firebug.Errors = extend(Firebug.Module, 43 { 44 dispatchName: "errors", 45 46 clear: function(context) 47 { 48 this.setCount(context, 0); // reset the UI counter 49 delete context.droppedErrors; // clear the counts of dropped errors 50 }, 51 52 increaseCount: function(context) 53 { 54 this.setCount(context, context.errorCount + 1) 55 }, 56 57 setCount: function(context, count) 58 { 59 context.errorCount = count; 60 61 if (context == FirebugContext) 62 this.showCount(context.errorCount); 63 }, 64 65 showMessageOnStatusBar: function(error) 66 { 67 if (statusText && statusBar && Firebug.breakOnErrors && error.message && !(error.flags & WARNING_FLAG)) // sometimes statusText is undefined..how? 68 { 69 statusText.setAttribute("value", error.message); 70 statusBar.setAttribute("errors", "true"); 71 if (FBTrace.DBG_ERRORS) FBTrace.sysout("errors.showMessageOnStatusBar error.message:"+error.message+"\n"); 72 } 73 }, 74 75 showCount: function(errorCount) 76 { 77 if (!statusBar) 78 return; 79 80 if (errorCount) 81 { 82 if (Firebug.showErrorCount) 83 { 84 var errorLabel = $STRP("plural.Error_Count", [errorCount]); 85 statusText.setAttribute("value", errorLabel); 86 } 87 88 statusBar.setAttribute("errors", "true"); 89 } 90 else 91 { 92 statusText.setAttribute("value", ""); 93 statusBar.removeAttribute("errors"); 94 } 95 }, 96 97 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 98 // Called by Console 99 100 startObserving: function() 101 { 102 consoleService.registerListener(this); 103 this.isObserving = true; 104 }, 105 106 stopObserving: function() 107 { 108 consoleService.unregisterListener(this); 109 this.isObserving = false; 110 }, 111 112 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 113 // extends consoleListener 114 115 observe: function(object) 116 { 117 try 118 { 119 if (window.closed) 120 this.stopObserving(); 121 if (typeof FBTrace == 'undefined') 122 return; 123 if (!FBTrace) 124 return; 125 } 126 catch(exc) 127 { 128 return; 129 } 130 131 try 132 { 133 if (object instanceof nsIScriptError) // all branches should trace 'object' 134 { 135 if (FBTrace.DBG_ERRORS) 136 FBTrace.sysout("errors.observe nsIScriptError: "+object.errorMessage, object); 137 138 var context = this.getErrorContext(object); // after instanceof 139 var isWarning = object.flags & WARNING_FLAG; // This cannot be pulled in front of the instanceof 140 context = this.logScriptError(context, object, isWarning); 141 if(!context) 142 return; 143 } 144 else 145 { 146 var isWarning = object.flags & WARNING_FLAG; 147 if (Firebug.showChromeMessages) 148 { 149 if (object instanceof nsIConsoleMessage) 150 { 151 if (FBTrace.DBG_ERRORS) 152 FBTrace.sysout("errors.observe nsIConsoleMessage: "+object.message, object); 153 154 var context = this.getErrorContext(object); // after instanceof 155 var msgId = lessTalkMoreAction(context, object, isWarning); 156 if (!msgId) 157 return; 158 if (context) 159 Firebug.Console.log(object.message, context, "consoleMessage", FirebugReps.Text); 160 } 161 else if (object.message) 162 { 163 if (FBTrace.DBG_ERRORS) 164 FBTrace.sysout("errors.observe object.message:", object); 165 166 var context = this.getErrorContext(object); 167 if (context) // maybe just FirebugContext 168 Firebug.Console.log(object.message, context, "consoleMessage", FirebugReps.Text); 169 else 170 FBTrace.sysout("errors.observe, no context for message", object); 171 } 172 else 173 FBTrace.sysout("errors.observe, no message in object", object); 174 } 175 else 176 { 177 if (FBTrace.DBG_ERRORS) 178 FBTrace.sysout("errors.observe showChromeMessages off, dropped:", object); 179 return; 180 } 181 } 182 if (FBTrace.DBG_ERRORS) 183 { 184 if (context) 185 { 186 if (context.window) 187 FBTrace.sysout((isWarning?"warning":"error")+" logged to "+ context.getName()); 188 else 189 { 190 FBTrace.sysout("errors.observe, context with no window, "+(isWarning?"warning":"error")+" object:", object); 191 FBTrace.sysout("errors.observe, context with no window, context:", context); 192 } 193 } 194 else 195 FBTrace.sysout("errors.observe, no context!\n"); 196 } 197 } 198 catch (exc) 199 { 200 // Errors prior to console init will come out here, eg error message from Firefox startup jjb. 201 if (FBTrace.DBG_ERRORS) 202 { 203 FBTrace.sysout("errors.observe FAILS "+exc, exc); 204 FBTrace.sysout("errors.observe object "+object, object); 205 } 206 } 207 }, 208 209 logScriptError: function(context, object, isWarning) 210 { 211 if (!context) 212 return; 213 214 if (FBTrace.DBG_ERRORS) 215 FBTrace.sysout("errors.observe logScriptError "+(Firebug.errorStackTrace?"have ":"NO ")+"errorStackTrace error object:", {object: object, errorStackTrace: Firebug.errorStackTrace}); 216 217 var category = getBaseCategory(object.category); 218 var isJSError = category == "js" && !isWarning; 219 220 var error = new ErrorMessage(object.errorMessage, object.sourceName, 221 object.lineNumber, object.sourceLine, category, context, null, msgId); // the sourceLine will cause the source to be loaded. 222 223 if (Firebug.showStackTrace && Firebug.errorStackTrace) 224 { 225 error.correctWithStackTrace(Firebug.errorStackTrace); 226 } 227 else if (checkForUncaughtException(context, object)) 228 { 229 context = getExceptionContext(context); 230 correctLineNumbersOnExceptions(object, error); 231 } 232 233 var msgId = lessTalkMoreAction(context, object, isWarning); 234 if (!msgId) 235 return null; 236 237 Firebug.errorStackTrace = null; // clear global: either we copied it or we don't use it. 238 239 if (!isWarning) 240 this.increaseCount(context); 241 242 var className = isWarning ? "warningMessage" : "errorMessage"; 243 244 if (context) 245 { 246 if (FBTrace.DBG_ERRORS) FBTrace.sysout("errors.observe delayed log to "+context.getName()+"\n"); 247 // then report later to avoid loading sourceS 248 context.throttle(this.delayedLogging, this, [msgId, context, error, context, className, false, true], true); 249 } 250 else 251 { 252 if (FBTrace.DBG_ERRORS) FBTrace.sysout("errors.observe direct log to FirebugContext"+FirebugContext+"\n"); 253 Firebug.Console.log(error, FirebugContext, className); 254 } 255 return context; 256 }, 257 258 delayedLogging: function() 259 { 260 var args = cloneArray(arguments); 261 var msgId = args.shift(); 262 var context = args.shift(); 263 var row = Firebug.Console.log.apply(Firebug.Console, args); 264 return row; 265 }, 266 267 getErrorContext: function(object) 268 { 269 var url = object.sourceName; 270 if(!url) 271 return FirebugContext; // eg some XPCOM messages 272 273 var errorContext = null; 274 TabWatcher.iterateContexts( 275 function findContextByURL(context) 276 { 277 if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS) 278 FBTrace.sysout("findContextByURL "+context.getName()); 279 280 if (!context.window || !context.getWindowLocation()) 281 return false; 282 283 if (context.getWindowLocation().toString() == url) 284 { 285 if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS) 286 FBTrace.sysout("findContextByURL found match to context window location"); 287 return errorContext = context; 288 } 289 else 290 { 291 if (context.sourceFileMap && context.sourceFileMap[url]) 292 { 293 if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS) 294 FBTrace.sysout("findContextByURL found match in sourceFileMap"); 295 return errorContext = context; 296 } 297 } 298 299 if (context.loaded) 300 { 301 if (FBL.getStyleSheetByHref(url, context)) 302 { 303 if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS) 304 FBTrace.sysout("findContextByURL found match to in loaded styleSheetMap"); 305 return errorContext = context; 306 } 307 else 308 return false; 309 } 310 else // then new stylesheets are still coming in. 311 { 312 if (FBL.getStyleSheetByHref(url, context)) 313 { 314 if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS) 315 FBTrace.sysout("findContextByURL found match to in non-loaded styleSheetMap"); 316 errorContext = context; // but we already have this one. 317 } 318 delete context.styleSheetMap; // clear the cache for next time. 319 } 320 } 321 ); 322 323 if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS && 'initTime' in this) 324 { 325 var deltaT = new Date().getTime() - this.initTime.getTime(); 326 FBTrace.sysout("errors.getErrorContext sheets: "+FBL.totalSheets+" rules: "+FBL.totalRules+" time: "+deltaT); 327 } 328 if (FBTrace.DBG_ERRORS && !errorContext) 329 FBTrace.sysout("errors.getErrorContext no context from error filename:"+url, object); 330 331 if (!errorContext) 332 { 333 if (FBTrace.DBG_ERRORS) 334 FBTrace.sysout("errors.getErrorContext no context from error filename:"+url, object); 335 errorContext = FirebugContext; // this is problem if the user isn't viewing the page with errors 336 } 337 338 if (FBTrace.DBG_ERRORS && !FirebugContext) 339 FBTrace.sysout("errors.observe, no FirebugContext in "+window.location+"\n"); 340 341 return errorContext; // we looked everywhere... 342 }, 343 344 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 345 // extends Module 346 347 initContext: function(context) 348 { 349 this.clear(context); 350 351 if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS) 352 { 353 FBL.totalSheets = 0; 354 FBL.totalRules = 0; 355 this.initTime = new Date(); 356 } 357 }, 358 359 showContext: function(browser, context) 360 { 361 this.showCount(context ? context.errorCount : 0); 362 }, 363 364 unwatchWindow: function(context, win) // called for top window and frames. 365 { 366 this.clear(context); // If we ever get errors by window from Firefox we can cache by window. 367 }, 368 369 destroyContext: function(context, persistedState) 370 { 371 this.showCount(0); 372 if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS && 'initTime' in this) 373 { 374 var deltaT = new Date().getTime() - this.initTime.getTime(); 375 FBTrace.sysout("errors.destroyContext sheets: "+FBL.totalSheets+" rules: "+FBL.totalRules+" time: "+deltaT); 376 } 377 }, 378 379 updateOption: function(name, value) 380 { 381 this.checkEnabled(); 382 }, 383 384 checkEnabled: function() 385 { 386 if (this.mustBeEnabled()) 387 { 388 if(!this.isObserving) 389 this.startObserving(); 390 // else we must be and we are observing 391 } 392 else 393 { 394 if (this.isObserving) 395 this.stopObserving(); 396 // else we must not be and we are not 397 } 398 if (FBTrace.DBG_ERRORS) 399 FBTrace.sysout("errors.checkEnabled mustBeEnabled: "+this.mustBeEnabled()+" isObserving:"+this.isObserving); 400 }, 401 402 mustBeEnabled: function() 403 { 404 const optionMap = {showJSErrors:1, showJSWarnings:1, showCSSErrors:1, showXMLErrors: 1, 405 showChromeErrors: 1, showChromeMessages: 1, showExternalErrors: 1, showXMLHttpRequests: 1, 406 showStackTrace: 1}; 407 408 for (var p in optionMap) 409 { 410 if (Firebug[p]) 411 return true; 412 } 413 return false; 414 }, 415 // ****************************************************************************** 416 417 reparseXPC: function(errorMessage, context) 418 { 419 var reXPCError = /JavaScript Error:\s*\"([^\"]*)\"/; 420 var reFile = /file:\s*\"([^\"]*)\"/; 421 var reLine = /line:\s*(\d*)/; 422 var m = reXPCError.exec(errorMessage); 423 if (!m) 424 return null; 425 var msg = m[1]; 426 427 var sourceFile = null; 428 m = reFile.exec(errorMessage); 429 if (m) 430 sourceFile = m[1]; 431 432 var sourceLineNo = 0; 433 m = reLine.exec(errorMessage); 434 if (m) 435 sourceLineNo = m[1]; 436 437 var sourceLine = null; 438 if (sourceFile && sourceLineNo && sourceLineNo != 0) 439 sourceLine = context.sourceCache.getLine(sourceFile, sourceLineNo); 440 441 var error = new ErrorMessage(msg, sourceFile, 442 sourceLineNo, sourceLine, "error", context, null); 443 return error; 444 } 445 }); 446 447 // ************************************************************************************************ 448 // Local Helpers 449 450 const categoryMap = 451 { 452 "javascript": "js", 453 "JavaScript": "js", 454 "DOM": "js", 455 "Events": "js", 456 "CSS": "css", 457 "XML": "xml", 458 "malformed-xml": "xml" 459 }; 460 461 function getBaseCategory(categories) 462 { 463 var categoryList = categories.split(" "); 464 for (var i = 0 ; i < categoryList.length; ++i) 465 { 466 var category = categoryList[i]; 467 if ( categoryMap.hasOwnProperty(category) ) 468 return categoryMap[category]; 469 } 470 } 471 472 function whyNotShown(url, category, isWarning) 473 { 474 var m = urlRe.exec(url); 475 var errorScheme = m ? m[1] : ""; 476 if (errorScheme == "javascript") 477 return null; 478 479 var isChrome = false; 480 481 if (!category) 482 return Firebug.showChromeErrors ? null :"no category, assume chrome, showChromeErrors false"; 483 484 var categories = category.split(" "); 485 for (var i = 0 ; i < categories.length; ++i) 486 { 487 var category = categories[i]; 488 if (category == "CSS" && !Firebug.showCSSErrors) 489 return "showCSSErrors"; 490 else if ((category == "XML" || category == "malformed-xml" ) && !Firebug.showXMLErrors) 491 return "showXMLErors"; 492 else if ((category == "javascript" || category == "JavaScript" || category == "DOM") 493 && !isWarning && !Firebug.showJSErrors) 494 return "showJSErrors"; 495 else if ((category == "javascript" || category == "JavaScript" || category == "DOM") 496 && isWarning && !Firebug.showJSWarnings) 497 return "showJSWarnings"; 498 else if (errorScheme == "chrome" || category == "XUL" || category == "chrome" || category == "XBL" 499 || category == "component") 500 isChrome = true; 501 } 502 503 if ((isChrome && !Firebug.showChromeErrors)) 504 return "showChromeErrors"; 505 506 return null; 507 } 508 509 function domainFilter(url) // never called? 510 { 511 if (Firebug.showExternalErrors) 512 return true; 513 514 var browserWin = document.getElementById("content").contentWindow; 515 516 var m = urlRe.exec(browserWin.location.href); 517 if (!m) 518 return false; 519 520 var browserDomain = m[3]; 521 522 m = urlRe.exec(url); 523 if (!m) 524 return false; 525 526 var errorScheme = m[1]; 527 var errorDomain = m[3]; 528 529 return errorScheme == "javascript" 530 || errorScheme == "chrome" 531 || errorDomain == browserDomain; 532 } 533 534 function lessTalkMoreAction(context, object, isWarning) 535 { 536 if (!context) 537 { 538 if (FBTrace.DBG_ERRORS) 539 FBTrace.sysout("errors.observe dropping "+object.category+" no context"); 540 return false; 541 } 542 543 var enabled = Firebug.Console.isAlwaysEnabled(); 544 if (!enabled) { 545 return null; 546 } 547 548 var why = whyNotShown(object.sourceName, object.category, isWarning); 549 550 if (why) 551 { 552 if (FBTrace.DBG_ERRORS) 553 FBTrace.sysout("errors.observe dropping "+object.category+" because: "+why); 554 555 context.droppedErrors = context.droppedErrors || {}; 556 if (!context.droppedErrors[object.category]) 557 context.droppedErrors[object.category] = 1; 558 else 559 context.droppedErrors[object.category] += 1; 560 561 return null; 562 } 563 564 var incoming_message = object.errorMessage; // nsIScriptError 565 if (!incoming_message) // nsIConsoleMessage 566 incoming_message = object.message; 567 568 if (Firebug.suppressPointlessErrors) 569 { 570 for (var msg in pointlessErrors) 571 { 572 573 if( msg.charAt(0) == incoming_message.charAt(0) ) 574 { 575 if (incoming_message.indexOf(msg) == 0) 576 { 577 if (FBTrace.DBG_ERRORS) 578 FBTrace.sysout("errors.observe dropping pointlessError: "+msg+"\n"); 579 return null; 580 } 581 } 582 } 583 } 584 585 var msgId = [incoming_message, object.sourceName, object.lineNumber].join("/"); 586 587 return msgId; 588 } 589 590 function checkForUncaughtException(context, object) 591 { 592 if (object.flags & object.exceptionFlag) 593 { 594 if (FBTrace.DBG_ERRORS) FBTrace.sysout("errors.observe is exception\n"); 595 if (reUncaught.test(object.errorMessage)) 596 { 597 if (FBTrace.DBG_ERRORS) FBTrace.sysout("uncaught exception matches "+reUncaught+"\n"); 598 if (context.thrownStackTrace) 599 { 600 Firebug.errorStackTrace = context.thrownStackTrace; 601 if (FBTrace.DBG_ERRORS) FBTrace.sysout("errors.observe trace.frames", context.thrownStackTrace.frames); 602 delete context.thrownStackTrace; 603 } 604 else 605 { 606 if (FBTrace.DBG_ERRORS) FBTrace.sysout("errors.observe NO context.thrownStackTrace\n"); 607 } 608 return true; 609 } 610 else 611 { 612 if (FBTrace.DBG_ERRORS) FBTrace.sysout("errors.observe not an uncaught exception\n"); 613 } 614 } 615 delete context.thrownStackTrace; 616 return false; 617 } 618 619 function getExceptionContext(context) 620 { 621 var errorWin = fbs.lastErrorWindow; // not available unless Script panel is enabled. 622 if (errorWin) 623 { 624 var errorContext = TabWatcher.getContextByWindow(errorWin); 625 if (FBTrace.DBG_ERRORS) 626 FBTrace.sysout("errors.observe exception context:"+(errorContext?errorContext.getName():"none")+" errorWin: "+errorWin+"\n"); 627 if (errorContext) 628 return errorContext; 629 } 630 return context; 631 } 632 633 function correctLineNumbersOnExceptions(object, error) 634 { 635 var m = reException.exec(object.errorMessage); 636 if (m) 637 { 638 var exception = m[1]; 639 if (exception) 640 errorMessage = "uncaught exception: "+exception; 641 var nsresult = m[2]; 642 if (nsresult) 643 errorMessage += " ("+nsresult+")"; 644 var sourceName = m[3]; 645 var lineNumber = parseInt(m[4]); 646 647 error.correctSourcePoint(sourceName, lineNumber); 648 649 if (FBTrace.DBG_ERRORS) 650 FBTrace.sysout("errors.correctLineNumbersOnExceptions corrected message with sourceName: "+sourceName+"@"+lineNumber); 651 } 652 } 653 654 // ************************************************************************************************ 655 656 Firebug.registerModule(Errors); 657 658 // ************************************************************************************************ 659 660 }}); 661