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