1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/firebug",
  6     "firebug/lib/url",
  7     "firebug/js/sourceLink",
  8     "firebug/js/stackFrame",
  9 ],
 10 function(Obj, Firebug, Url, SourceLink, StackFrame) {
 11 
 12 // ********************************************************************************************* //
 13 // Constants
 14 
 15 const Cc = Components.classes;
 16 const Ci = Components.interfaces;
 17 
 18 const PCMAP_SOURCETEXT = Ci.jsdIScript.PCMAP_SOURCETEXT;
 19 const PCMAP_PRETTYPRINT = Ci.jsdIScript.PCMAP_PRETTYPRINT;
 20 
 21 var jsd = Cc["@mozilla.org/js/jsd/debugger-service;1"].getService(Ci.jsdIDebuggerService);
 22 
 23 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 24 
 25 /**
 26  * SourceFile one for every compilation unit.
 27  * Unique URL for each. (href)
 28  * Unique outerScript, the statements outside of any function defintion
 29  * sourceCache keyed by href has source for this compilation unit
 30  * Stored by href in context.
 31  * Contains array of jsdIScript for functions (scripts) defined in this unit
 32  * May contain line table (for sources viewed)
 33  */
 34 Firebug.SourceFile = function (compilation_unit_type)
 35 {
 36     this.compilation_unit_type = compilation_unit_type;
 37 }
 38 
 39 Firebug.SourceFile.prototype =
 40 {
 41     getBaseLineOffset: function()
 42     {
 43         return 0;
 44     },
 45 
 46     getURL: function()
 47     {
 48         return this.href;
 49     },
 50 
 51     toString: function()
 52     {
 53         var str = (this.compilation_unit_type?this.compilation_unit_type + " " : "") +
 54             this.href + " script.tags( ";
 55 
 56         if (this.outerScript)
 57             str += (this.outerScript.isValid?this.outerScript.tag:"X") +"| ";
 58 
 59         if (this.innerScripts)
 60         {
 61             var numberInvalid = 0;
 62             for (var p in this.innerScripts)
 63             {
 64                 var script = this.innerScripts[p];
 65                 if (script.isValid)
 66                     str += p+" ";
 67                 else
 68                     numberInvalid++;
 69             }
 70         }
 71 
 72         str += ")" + (numberInvalid ? "(" + numberInvalid + " invalid)" : "");
 73         return str;
 74     },
 75 
 76     forEachScript: function(callback)
 77     {
 78          if (this.outerScript)
 79              callback(this.outerScript);
 80 
 81          if (this.innerScripts)
 82          {
 83              for (var p in this.innerScripts)
 84              {
 85                  var script = this.innerScripts[p];
 86                  var rc = callback(script);
 87                  if (rc)
 88                      return rc;
 89              }
 90          }
 91     },
 92 
 93     getLineRanges: function()
 94     {
 95         var str = "";
 96         this.forEachScript(function appendARange(script)
 97         {
 98             var endLineNumber = script.baseLineNumber + script.lineExtent;
 99             str += " "+script.baseLineNumber +"-("+script.tag+")-"+endLineNumber;
100         });
101 
102         return str;
103     },
104 
105     getSourceLength: function()
106     {
107         return this.sourceLength;
108     },
109 
110     getLine: function(context, lineNo)
111     {
112         return context.sourceCache.getLine(this.href, lineNo);
113     },
114 
115     addToLineTable: function(script)
116     {
117         if (!script || !script.isValid)
118         {
119             if (FBTrace.DBG_ERRORS)
120                 FBTrace.sysout("addToLineTable got invalid script " +
121                     (script ? script.tag : "null"));
122             return;
123         }
124 
125         // For outer scripts, a better algorithm would loop over PC, use pcToLine to mark the lines.
126         // This assumes there are fewer PCs in an outer script than lines, probably true for large
127         // systems.
128         // And now addToLineTable is only used for outerScripts (eval and top-level).
129         // But since we can't know the range of PC values we cannot use that approach.
130 
131         if (!this.outerScriptLineMap)
132             this.outerScriptLineMap = [];
133 
134         var lineCount = script.lineExtent + 1;
135         var offset = this.getBaseLineOffset();
136 
137         if (FBTrace.DBG_LINETABLE)
138         {
139             FBTrace.sysout("lib.SourceFile.addToLineTable script.tag:" + script.tag +
140                 " lineExtent=" + lineCount + " baseLineNumber=" + script.baseLineNumber +
141                 " offset=" + offset + " for " + this.compilation_unit_type);
142             var startTime = new Date().getTime();
143         }
144 
145         // isLineExecutable requires about 1ms per line, so it can only be called for toy programs
146         if (lineCount > 100)
147             lineCount = 100;
148 
149         for (var i = 0; i <= lineCount; i++)
150         {
151             // the max is (i + script.baseLineNumber + script.lineExtent)
152             var scriptLineNo = i + script.baseLineNumber;
153             var mapLineNo = scriptLineNo - offset;
154             try
155             {
156                 if (script.isLineExecutable(scriptLineNo, this.pcmap_type))
157                     this.outerScriptLineMap.push(mapLineNo);
158             }
159             catch (e)
160             {
161                 // I guess not...
162             }
163 
164             if (FBTrace.DBG_LINETABLE)
165             {
166                 var pcFromLine = script.lineToPc(scriptLineNo, this.pcmap_type);
167                 var lineFromPC = script.pcToLine(pcFromLine, this.pcmap_type);
168 
169                 if (this.outerScriptLineMap.indexOf(mapLineNo) != -1)
170                 {
171                     FBTrace.sysout("lib.SourceFile.addToLineTable ["+mapLineNo+"]="+script.tag+
172                         " for scriptLineNo="+scriptLineNo+" vs "+lineFromPC+
173                         "=lineFromPC; lineToPc="+pcFromLine+" with map="+
174                         (this.pcmap_type==PCMAP_PRETTYPRINT?"PP":"SOURCE"));
175                 }
176                 else
177                 {
178                     FBTrace.sysout("lib.SourceFile.addToLineTable not executable scriptLineNo="+
179                         scriptLineNo+" vs "+lineFromPC+"=lineFromPC; lineToPc="+pcFromLine);
180                 }
181             }
182         }
183 
184         if (FBTrace.DBG_LINETABLE)
185         {
186             var endTime = new Date().getTime();
187             var delta = endTime - startTime ;
188             if (delta > 0)
189             {
190                 FBTrace.sysout("SourceFile.addToLineTable processed "+lineCount+" lines in "+
191                     delta+" millisecs "+Math.round(lineCount/delta)+" lines per millisecond");
192             }
193 
194             FBTrace.sysout("SourceFile.addToLineTable: "+this.toString());
195         }
196      },
197 
198      addToLineTableByPCLoop: function(script)
199      {
200         // This code is not called; it crashes FF3pre
201         // https://bugzilla.mozilla.org/show_bug.cgi?id=430205
202         if (!this.outerScriptLineMap)
203             this.outerScriptLineMap = {};
204 
205         var lineCount = script.lineExtent;
206         var offset = this.getBaseLineOffset();
207         if (FBTrace.DBG_LINETABLE)
208         {
209             FBTrace.sysout("lib.SourceFile.addToLineTableByPCLoop script.tag:"+script.tag+
210                 " lineCount="+lineCount+" offset="+offset+" for "+this.compilation_unit_type);
211             var startTime = new Date().getTime();
212         }
213 
214         for (var i = 0; i <= 10*lineCount; i++)
215         {
216             var lineFromPC = script.pcToLine(i, this.pcmap_type);
217             //FBTrace.sysout("lib.SourceFile.addToLineTableByPCLoop pc="+i+" line: "+lineFromPC+"\n");
218             this.outerScriptLineMap[lineFromPC] = script;
219             if (lineFromPC >= lineCount)
220                 break;
221         }
222 
223         if (FBTrace.DBG_LINETABLE)
224         {
225             FBTrace.sysout("SourceFile.addToLineTableByPCLoop: "+this.toString()+"\n");
226             var endTime = new Date().getTime();
227             var delta = endTime - startTime ;
228 
229             if (delta > 0)
230             {
231                 FBTrace.sysout("SourceFileaddToLineTableByPCLoop processed "+lineCount+
232                     " lines in "+delta+" millisecs "+Math.round(lineCount/delta)+
233                     " lines per millisecond\n");
234             }
235         }
236     },
237 
238     hasScriptAtLineNumber: function(lineNo, mustBeExecutableLine)
239     {
240         var offset = this.getBaseLineOffset();
241 
242         if (!this.innerScripts)
243             return; // eg URLOnly
244 
245         // lineNo is user-viewed number, targetLineNo is jsd number
246         var targetLineNo = lineNo + offset;
247 
248         var scripts = [];
249         for (var p in this.innerScripts)
250         {
251             var script = this.innerScripts[p];
252             if (mustBeExecutableLine && !script.isValid)
253                 continue;
254 
255             this.addScriptAtLineNumber(scripts, script, targetLineNo,
256                 mustBeExecutableLine, offset);
257 
258             if (scripts.length)
259                 return true;
260         }
261 
262         if (this.outerScript && !(mustBeExecutableLine && !this.outerScript.isValid))
263         {
264             this.addScriptAtLineNumber(scripts, this.outerScript, targetLineNo,
265                 mustBeExecutableLine, offset);
266         }
267 
268         return (scripts.length > 0);
269     },
270 
271     getScriptsAtLineNumber: function(lineNo, mustBeExecutableLine)
272     {
273         var offset = this.getBaseLineOffset();
274 
275         if (!this.innerScripts)
276             return; // eg URLOnly
277 
278         // lineNo is user-viewed number, targetLineNo is jsd number
279         var targetLineNo = lineNo + offset;
280 
281         var scripts = [];
282         for (var p in this.innerScripts)
283         {
284             var script = this.innerScripts[p];
285             if (mustBeExecutableLine && !script.isValid)
286                 continue;
287 
288             this.addScriptAtLineNumber(scripts, script, targetLineNo,
289                 mustBeExecutableLine, offset);
290         }
291 
292         if (this.outerScript && !(mustBeExecutableLine && !this.outerScript.isValid))
293         {
294             this.addScriptAtLineNumber(scripts, this.outerScript, targetLineNo,
295                 mustBeExecutableLine, offset);
296         }
297 
298         if (FBTrace.DBG_LINETABLE)
299         {
300             if (scripts.length < 1)
301             {
302                 FBTrace.sysout("lib.getScriptsAtLineNumber no targetScript at "+lineNo,
303                     " for sourceFile:"+this.toString());
304                 return false;
305             }
306             else
307             {
308                 FBTrace.sysout("getScriptsAtLineNumber offset "+offset+" for sourcefile: "+
309                     this.toString());
310             }
311         }
312 
313         return (scripts.length > 0) ? scripts : false;
314      },
315 
316      addScriptAtLineNumber: function(scripts, script, targetLineNo, mustBeExecutableLine, offset)
317      {
318         // script.isValid will be true.
319         if (FBTrace.DBG_LINETABLE)
320             FBTrace.sysout("addScriptAtLineNumber trying "+script.tag+", is "+
321                 script.baseLineNumber+" <= "+targetLineNo +" <= "+ (script.baseLineNumber +
322                 script.lineExtent)+"? using offset = "+offset+"\n");
323 
324         if (targetLineNo >= script.baseLineNumber)
325         {
326             if ((script.baseLineNumber + script.lineExtent) >= targetLineNo)
327             {
328                 if (mustBeExecutableLine)
329                 {
330                     try
331                     {
332                         if (!script.isLineExecutable(targetLineNo, this.pcmap_type) )
333                         {
334                             if (FBTrace.DBG_LINETABLE)
335                                 FBTrace.sysout("getScriptsAtLineNumber tried "+script.tag+
336                                     ", not executable at targetLineNo:"+targetLineNo+" pcmap:"+
337                                     this.pcmap_type);
338                             return;
339                         }
340                     }
341                     catch (e)
342                     {
343                         // Component returned failure code: 0x80040111 (NS_ERROR_NOT_AVAILABLE)
344                         // [jsdIScript.isLineExecutable]
345                         return;
346                     }
347                 }
348 
349                 scripts.push(script);
350 
351                 if (FBTrace.DBG_LINETABLE)
352                 {
353                     var checkExecutable = "";
354                     if (mustBeExecutableLine)
355                         checkExecutable = " isLineExecutable: "+
356                         script.isLineExecutable(targetLineNo, this.pcmap_type)+"@pc:"+
357                         script.lineToPc(targetLineNo, this.pcmap_type);
358 
359                     FBTrace.sysout("getScriptsAtLineNumber found "+script.tag+", isValid: "+
360                         script.isValid+" targetLineNo:"+targetLineNo+checkExecutable);
361                 }
362             }
363         }
364     },
365 
366     scriptsIfLineCouldBeExecutable: function(lineNo)  // script may not be valid
367     {
368         var scripts = this.getScriptsAtLineNumber(lineNo, true);
369 
370         if (FBTrace.DBG_LINETABLE && !scripts)
371             FBTrace.sysout("lib.scriptsIfLineCouldBeExecutable this.outerScriptLineMap",
372                 this.outerScriptLineMap);
373 
374         if (!scripts && this.outerScriptLineMap && (this.outerScriptLineMap.indexOf(lineNo) != -1))
375             return [this.outerScript];
376 
377         return scripts;
378     },
379 
380     isExecutableLine: function(lineNo)  // script may not be valid
381     {
382         if (this.hasScriptAtLineNumber(lineNo, true))
383            return true;
384 
385         if (this.outerScriptLineMap && (this.outerScriptLineMap.indexOf(lineNo) != -1))
386             return true;
387 
388         return false;
389     },
390 
391     hasScript: function(script)
392     {
393         if (this.outerScript && (this.outerScript.tag == script.tag) )
394             return true;
395 
396         // XXXjjb Don't use indexOf or similar tests that rely on ===, since we are really
397         // working with wrappers around jsdIScript, not script themselves.  I guess.
398 
399         return (this.innerScripts && this.innerScripts.hasOwnProperty(script.tag));
400     },
401 
402     // these objects map JSD's values to correct values
403     getScriptAnalyzer: function(script)
404     {
405         if (script && this.outerScript && (script.tag == this.outerScript.tag) )
406             return this.getOuterScriptAnalyzer();
407 
408         return new Firebug.SourceFile.NestedScriptAnalyzer(this);
409     },
410 
411     // return.path: group/category label, return.name: item label
412     getObjectDescription: function()
413     {
414         return Url.splitURLBase(this.href);
415     },
416 
417     isEval: function()
418     {
419         return (this.compilation_unit_type == "eval-level") ||
420             (this.compilation_unit_type == "newFunction");
421     },
422 
423     isEvent: function()
424     {
425         return (this.compilation_unit_type == "event");
426     },
427 
428     loadScriptLines: function(context)  // array of lines
429     {
430         if (this.source)
431             return this.source;
432         else if (context.sourceCache)
433             return context.sourceCache.load(this.href);
434         else if (FBTrace.DBG_ERRORS)
435             FBTrace.sysout("sourceFile.loadScriptLines FAILS no sourceCache "+
436                 context.getName(), context);
437     },
438 
439     getOuterScriptAnalyzer: function()
440     {
441         FBTrace.sysout("getOuterScriptAnalyzer not overridden for "+sourceFile, this);
442     },
443 }
444 
445 Firebug.SourceFile.summarizeSourceLineArray = function(sourceLines, size)
446 {
447     var buf  = "";
448     for (var i = 0; i < sourceLines.length; i++)
449      {
450          var aLine = sourceLines[i].substr(0,240);  // avoid huge lines
451          buf += aLine.replace(/\s/, " ", "g");
452          if (buf.length > size || aLine.length > 240)
453              break;
454      }
455      return buf.substr(0, size);
456 };
457 
458 
459 Firebug.SourceFile.NestedScriptAnalyzer = function(sourceFile)
460 {
461     this.sourceFile = sourceFile;
462 }
463 
464 Firebug.SourceFile.NestedScriptAnalyzer.prototype =
465 {
466     // Adjust JSD line numbers based on origin of script
467     getSourceLineFromFrame: function(context, frame)
468     {
469         if (FBTrace.DBG_SOURCEFILES)
470             FBTrace.sysout("NestedScriptAnalyzer in "+this.sourceFile.compilation_unit_type+
471                 ": frame.line  - this.sourceFile.getBaseLineOffset() "+
472                 frame.line +" - "+this.sourceFile.getBaseLineOffset());
473 
474         return frame.line - (this.sourceFile.getBaseLineOffset());
475     },
476 
477     // Interpret frame to give fn(args)
478     getFunctionDescription: function(script, context, frame)
479     {
480         if (frame)
481         {
482             var name = frame.name;
483             var args = StackFrame.getFunctionArgValues(frame);
484         }
485         else
486         {
487             var name = script.functionName;
488             var args = [];
489         }
490 
491         if (name == "anonymous")
492         {
493             var name = StackFrame.guessFunctionName(this.sourceFile.href,
494                 this.getBaseLineNumberByScript(script), context);
495         }
496 
497         return {name: name, args: args};
498     },
499 
500     // link to source for this script.
501     getSourceLinkForScript: function (script)
502     {
503         var line = this.getBaseLineNumberByScript(script);
504         return new SourceLink.SourceLink(this.sourceFile.href, line, "js");
505     },
506 
507     getBaseLineNumberByScript: function(script)
508     {
509         return script.baseLineNumber - (this.sourceFile.getBaseLineOffset() - 1);
510     }
511 }
512 
513 Firebug.SourceFile.addScriptsToSourceFile = function(sourceFile, outerScript, innerScripts)
514 {
515     // Attach the innerScripts for use later
516     if (!sourceFile.innerScripts)
517          sourceFile.innerScripts = {};
518 
519     var total = 0;
520     while (innerScripts.hasMoreElements())
521     {
522         var script = innerScripts.getNext();
523         if (!script || ((script instanceof Ci.jsdIScript) && !script.tag))
524         {
525             if (FBTrace.DBG_SOURCEFILES)
526                 FBTrace.sysout("addScriptsToSourceFile innerScripts.getNext FAILS "+
527                     sourceFile, script);
528             continue;
529         }
530 
531         sourceFile.innerScripts[script.tag] = script;
532 
533         if (FBTrace.DBG_SOURCEFILES)
534             total++;
535     }
536 
537     if (FBTrace.DBG_SOURCEFILES)
538         FBTrace.sysout("addScriptsToSourceFile "+ total +" scripts, sourcefile="+
539             sourceFile.toString(), sourceFile);
540 }
541 
542 // ********************************************************************************************* //
543 
544 Firebug.EvalLevelSourceFile = function(url, script, eval_expr, source, mapType,
545     innerScriptEnumerator)
546 {
547     this.href = url.href;
548     this.hrefKind = url.kind;
549     this.outerScript = script;
550     this.containingURL = script.fileName;
551     this.evalExpression = eval_expr;
552     this.sourceLength = source.length;
553     this.source = source;
554     this.pcmap_type = mapType;
555     Firebug.SourceFile.addScriptsToSourceFile(this, script, innerScriptEnumerator);
556 };
557 
558 Firebug.EvalLevelSourceFile.prototype =
559     Obj.descend(new Firebug.SourceFile("eval-level"), // shared prototype
560 {
561     getLine: function(context, lineNo)
562     {
563         return this.source[lineNo - 1];
564     },
565 
566     getBaseLineOffset: function()
567     {
568         // baseLineNumber always valid even after jsdIscript isValid false
569         return this.outerScript.baseLineNumber - 1;
570     },
571 
572     getObjectDescription: function()
573     {
574         if (this.hrefKind == "source" || this.hrefKind == "data")
575             return Url.splitURLBase(this.href);
576 
577         if (!this.summary)
578         {
579             if (this.evalExpression)
580                 this.summary = Firebug.SourceFile.summarizeSourceLineArray(
581                     this.evalExpression.substr(0, 240), 120);
582 
583             if (!this.summary)
584                 this.summary = "";
585 
586             if (this.summary.length < 120)
587                 this.summary = "eval("+this.summary + "...)=" +
588                     Firebug.SourceFile.summarizeSourceLineArray(this.source,
589                         120 - this.summary.length);
590         }
591 
592         var containingFileDescription = Url.splitURLBase(this.containingURL);
593 
594         if (FBTrace.DBG_SOURCEFILES)
595             FBTrace.sysout("EvalLevelSourceFile this.evalExpression.substr(0, 240):"+
596                 (this.evalExpression?this.evalExpression.substr(0, 240):"null")+" summary",
597                 this.summary);
598 
599         return {
600             path: containingFileDescription.path,
601             name: containingFileDescription.name+"/eval: "+this.summary
602         };
603     },
604 
605     getOuterScriptAnalyzer: function()
606     {
607         return new Firebug.EvalLevelSourceFile.OuterScriptAnalyzer(this);
608     },
609 });
610 
611 Firebug.EvalLevelSourceFile.OuterScriptAnalyzer = function(sourceFile)
612 {
613     this.sourceFile = sourceFile;
614 }
615 
616 Firebug.EvalLevelSourceFile.OuterScriptAnalyzer.prototype =
617 {
618     // Adjust JSD line numbers based on origin of script
619     getSourceLineFromFrame: function(context, frame)
620     {
621         return frame.line - this.sourceFile.getBaseLineOffset();
622     },
623 
624     // Interpret frame to give fn(args)
625     getFunctionDescription: function(script, context, frame)
626     {
627         return {name: "eval", args: [this.evalExpression] };
628     },
629 
630     getSourceLinkForScript: function (script)
631     {
632         return new SourceLink.SourceLink(this.sourceFile.href, 1, "js");
633     }
634 }
635 
636 // ********************************************************************************************* //
637 
638 Firebug.EventSourceFile = function(url, script, title, source, innerScriptEnumerator)
639 {
640      this.href = url;
641      this.outerScript = script;
642      this.containingURL = script.fileName;
643      this.title = title;
644      this.source = source; // points to the sourceCache lines
645      this.sourceLength = source.length;
646      this.pcmap_type = PCMAP_PRETTYPRINT;
647 
648      Firebug.SourceFile.addScriptsToSourceFile(this, script, innerScriptEnumerator);
649 };
650 
651 Firebug.EventSourceFile.prototype = Obj.descend(new Firebug.SourceFile("event"),
652 {
653     getLine: function(context, lineNo)
654     {
655         return this.source[lineNo - 1];
656     },
657 
658     getBaseLineOffset: function()
659     {
660         return 1;
661     },
662 
663     getObjectDescription: function()
664     {
665         if (!this.summary)
666              this.summary = Firebug.SourceFile.summarizeSourceLineArray(this.source, 120);
667 
668         var containingFileDescription = Url.splitURLBase(this.containingURL);
669 
670         return {
671             path: containingFileDescription.path,
672             name: containingFileDescription.name+"/event: "+this.summary
673         };
674     },
675 
676     getOuterScriptAnalyzer: function()
677     {
678         return new Firebug.EventSourceFile.OuterScriptAnalyzer(this);
679     },
680 });
681 
682 Firebug.EventSourceFile.OuterScriptAnalyzer = function(sourceFile)
683 {
684     this.sourceFile = sourceFile;
685 }
686 
687 Firebug.EventSourceFile.OuterScriptAnalyzer.prototype =
688 {
689     // Adjust JSD line numbers based on origin of script
690     getSourceLineFromFrame: function(context, frame)
691     {
692         var script = frame.script;
693         var line = script.pcToLine(frame.pc, PCMAP_PRETTYPRINT);
694         return line - 1;
695     },
696 
697     // Interpret frame to give fn(args)
698     getFunctionDescription: function(script, context, frame)
699     {
700         var name = script.functionName;
701         if (!name)
702             name = "jsdbug_NoScriptFunctionName";
703 
704         if (frame)
705         {
706             var args = StackFrame.getFunctionArgValues(frame);
707         }
708         else
709         {
710             var args = []
711         }
712         return {name: name, args: args};
713     },
714 
715     getSourceLinkForScript: function (script)
716     {
717         return new SourceLink.SourceLink(this.sourceFile.href, 1, "js");
718     }
719 }
720 
721 // ********************************************************************************************* //
722 
723 Firebug.SourceFile.CommonBase =
724 {
725     getSourceLength: function()
726     {
727         if (!this.sourceLength)
728             this.sourceLength = this.context.sourceCache.load(this.href).length;
729         return this.sourceLength;
730     },
731 
732     getOuterScriptAnalyzer: function()
733     {
734         return Firebug.TopLevelSourceFile.OuterScriptAnalyzer;
735     },
736 }
737 
738 // ********************************************************************************************* //
739 
740 Firebug.TopLevelSourceFile = function(url, outerScript, sourceLength, innerScriptEnumerator)
741 {
742     this.href = url;
743     this.outerScript = outerScript;  // Beware may not be valid after we return!!
744     this.sourceLength = sourceLength;
745     this.pcmap_type = PCMAP_SOURCETEXT;
746 
747     Firebug.SourceFile.addScriptsToSourceFile(this, outerScript, innerScriptEnumerator);
748 }
749 
750 Firebug.TopLevelSourceFile.prototype = Obj.descend(new Firebug.SourceFile("top-level"),
751     Firebug.SourceFile.CommonBase);
752 
753 Firebug.TopLevelSourceFile.OuterScriptAnalyzer =
754 {
755     // Adjust JSD line numbers based on origin of script
756     getSourceLineFromFrame: function(context, frame)
757     {
758         return frame.line;
759     },
760     // Interpret frame to give fn(args)
761     getFunctionDescription: function(script, context, frame)
762     {
763         // this is more useful that just "top_level"
764         var file_name = Url.getFileName(Url.normalizeURL(script.fileName));
765         file_name = file_name ? file_name: "__top_level__";
766         return {name: file_name, args: []};
767     },
768     getSourceLinkForScript: function (script)
769     {
770         return SourceLink.SourceLink(Url.normalizeURL(script.fileName),
771             script.baseLineNumber, "js")
772     }
773 }
774 
775 // ********************************************************************************************* //
776 
777 // we don't have the outer script and we delay source load.
778 Firebug.EnumeratedSourceFile = function(url)
779 {
780     // may not be outerScript file name, eg this could be an enumerated eval
781     this.href = new String(url);
782     this.innerScripts = {};
783     this.pcmap_type = PCMAP_SOURCETEXT;
784 }
785 
786 Firebug.EnumeratedSourceFile.prototype = Obj.descend(
787     new Firebug.SourceFile("enumerated"),
788     Firebug.SourceFile.CommonBase);
789 
790 // ********************************************************************************************* //
791 
792 Firebug.NoScriptSourceFile = function(context, url) // Somehow we got the Url, but not the script
793 {
794     this.href = url;  // we know this much
795     this.innerScripts = {};
796 }
797 
798 Firebug.NoScriptSourceFile.prototype = Obj.descend(
799     new Firebug.SourceFile("URLOnly"),
800     Firebug.SourceFile.CommonBase);
801 
802 // ********************************************************************************************* //
803 // javascript in a .xul or .xml file, no outerScript
804 
805 Firebug.XULSourceFile = function(url, outerScript, innerScriptEnumerator)
806 {
807     this.href = url;
808     this.pcmap_type = PCMAP_SOURCETEXT;
809     this.outerScript = outerScript;  // Beware may not be valid after we return!!
810 
811     Firebug.SourceFile.addScriptsToSourceFile(this, outerScript, innerScriptEnumerator);
812 }
813 
814 Firebug.XULSourceFile.prototype = Obj.descend(
815     new Firebug.SourceFile("xul"),
816     Firebug.SourceFile.CommonBase);
817 
818 // ********************************************************************************************* //
819 
820 // element.appendChild(scriptTag)
821 Firebug.ScriptTagAppendSourceFile = function(url, outerScript, sourceLength, innerScriptEnumerator)
822 {
823     this.href = url;
824     this.outerScript = outerScript;  // Beware may not be valid after we return!!
825     this.sourceLength = sourceLength;
826     this.pcmap_type = PCMAP_SOURCETEXT;
827 
828     Firebug.SourceFile.addScriptsToSourceFile(this, outerScript, innerScriptEnumerator);
829 }
830 
831 Firebug.ScriptTagAppendSourceFile.prototype = Obj.descend(
832     new Firebug.SourceFile("scriptTagAppend"),
833     Firebug.SourceFile.CommonBase);
834 
835 // ********************************************************************************************* //
836 
837 // we don't have the outer script and we delay source load
838 Firebug.ScriptTagSourceFile = function(context, url, scriptTagNumber)
839 {
840     this.context = context;
841     this.href = url;  // we know this is not an eval
842     this.scriptTagNumber = scriptTagNumber;
843     this.innerScripts = {};
844     this.pcmap_type = PCMAP_SOURCETEXT;
845 }
846 
847 Firebug.ScriptTagSourceFile.prototype = Obj.descend(
848     new Firebug.SourceFile("scriptTag"),
849     Firebug.SourceFile.CommonBase);
850 
851 // ********************************************************************************************* //
852 
853 Firebug.SourceFile.getSourceFileByScript = function(context, script)
854 {
855     if (!context.sourceFileMap)
856          return null;
857 
858     // Other algorithms are possible:
859     //   We could store an index, context.sourceFileByTag
860     //   Or we could build a tree keyed by url, with SpiderMonkey script.fileNames at the top
861     //   and our urls below
862 
863     // we won't be lucky for file:/ urls, no normalizeURL applied
864     var lucky = context.sourceFileMap[script.fileName];
865     if (FBTrace.DBG_SOURCEFILES && lucky)
866         FBTrace.sysout("getSourceFileByScript trying to be lucky for "+
867             script.tag + " in "+lucky, script);
868 
869     if (lucky && lucky.hasScript(script))
870         return lucky;
871 
872     if (FBTrace.DBG_SOURCEFILES)
873         FBTrace.sysout("getSourceFileByScript looking for "+script.tag+"@"+script.fileName+" in "+
874             context.getName()+": ", context.sourceFileMap);
875 
876     for (var url in context.sourceFileMap)
877     {
878         var sourceFile = context.sourceFileMap[url];
879         if (sourceFile.hasScript(script))
880             return sourceFile;
881     }
882 };
883 
884 Firebug.SourceFile.getScriptAnalyzer = function(context, script)
885 {
886     var sourceFile = Firebug.SourceFile.getSourceFileByScript(context, script);
887     if (FBTrace.DBG_STACK)
888          FBTrace.sysout("getScriptAnalyzer "+ (sourceFile?"finds sourceFile: ":
889             "FAILS to find sourceFile"), sourceFile);
890 
891     if (sourceFile)
892     {
893         var analyzer = sourceFile.getScriptAnalyzer(script);
894         if (FBTrace.DBG_STACK)
895             FBTrace.sysout("getScriptAnalyzer finds analyzer: ", analyzer);
896 
897         return analyzer;
898     }
899 
900     return undefined;
901 };
902 
903 Firebug.SourceFile.getSourceFileAndLineByScript= function(context, script, frame)
904 {
905     var sourceFile = Firebug.SourceFile.getSourceFileByScript(context, script);
906     if (sourceFile)
907     {
908         if (sourceFile.pcmap_type)
909             var line = script.pcToLine(1, sourceFile.pcmap_type);
910         else
911             var line = 1;
912 
913         return { sourceFile: sourceFile, lineNo: line };
914     }
915 };
916 
917 Firebug.SourceFile.guessEnclosingFunctionName = function(url, line, context)
918 {
919     var sourceFile = context.sourceFileMap[url];
920     if (sourceFile)
921     {
922         var scripts = sourceFile.getScriptsAtLineNumber(line);
923         if (scripts)
924         {
925             // TODO try others?
926             var script = scripts[0];
927             var analyzer = sourceFile.getScriptAnalyzer(script);
928 
929             // Some analyzers don't implement this method.
930             if (analyzer.getBaseLineNumberByScript)
931                 line = analyzer.getBaseLineNumberByScript(script);
932         }
933     }
934 
935     return StackFrame.guessFunctionName(url, line-1, context);
936 };
937 
938 // ********************************************************************************************* //
939 // Functions
940 
941 Firebug.SourceFile.findScripts = function(context, url, line)
942 {
943     var sourceFile = context.sourceFileMap[url];
944     if (sourceFile)
945     {
946         var scripts = sourceFile.scriptsIfLineCouldBeExecutable(line);
947     }
948     else
949     {
950         if (FBTrace.DBG_STACK)
951             FBTrace.sysout("lib.findScript, no sourceFile in context for url=", url);
952     }
953     return scripts;
954 };
955 
956 Firebug.SourceFile.findScriptForFunctionInContext = function(context, fn)
957 {
958     var found = null;
959 
960     if (!fn || typeof(fn) !== 'function')
961         return found;
962 
963     try
964     {
965         var wrapped = jsd.wrapValue(fn);
966         found = wrapped.script;
967         if (!found)
968             found = wrapped.jsParent.script;
969 
970         if (!found && FBTrace.DBG_ERRORS)
971         {
972             FBTrace.sysout("findScriptForFunctionInContext ",
973                 {fn: fn, wrapValue: jsd.wrapValue(fn), found: found});
974         }
975     }
976     catch (err)
977     {
978         if (FBTrace.DBG_ERRORS)
979             FBTrace.sysout("sourceFile.findScriptForFunctionInContext; EXCEPTION " + err, err);
980     }
981 
982     if (FBTrace.DBG_FUNCTION_NAMES)
983         FBTrace.sysout("findScriptForFunctionInContext found " + (found ? found.tag : "none"));
984 
985     return found;
986 }
987 
988 Firebug.SourceFile.findSourceForFunction = function(fn, context)
989 {
990     var script = Firebug.SourceFile.findScriptForFunctionInContext(context, fn);
991     return script ? Firebug.SourceFile.getSourceLinkForScript(script, context) : null;
992 };
993 
994 Firebug.SourceFile.getSourceLinkForScript = function(script, context)
995 {
996     var sourceFile = Firebug.SourceFile.getSourceFileByScript(context, script);
997     if (sourceFile)
998     {
999         var scriptAnalyzer = sourceFile.getScriptAnalyzer(script);
1000         if (scriptAnalyzer)
1001             return scriptAnalyzer.getSourceLinkForScript(script);
1002         else
1003         {
1004             // no-op for detrace
1005             if (FBTrace.DBG_ERRORS)
1006                 FBTrace.sysout("getSourceLineForScript FAILS no scriptAnalyser for sourceFile "+
1007                     sourceFile);
1008         }
1009     }
1010 };
1011 
1012 // ********************************************************************************************* //
1013 // Source Files
1014 
1015 Firebug.SourceFile.getSourceFileByHref = function(url, context)
1016 {
1017     return context.sourceFileMap[url];
1018 };
1019 
1020 Firebug.SourceFile.sourceURLsAsArray = function(context)
1021 {
1022     var urls = [];
1023     var sourceFileMap = context.sourceFileMap;
1024     for (var url in sourceFileMap)
1025         urls.push(url);
1026 
1027     if (FBTrace.DBG_SOURCEFILES)
1028         FBTrace.sysout("sourceURLsAsArray urls="+urls.length+" in context "+context.getName());
1029 
1030     return urls;
1031 };
1032 
1033 // deprecated, use mapAsArray
1034 Firebug.SourceFile.sourceFilesAsArray = function(sourceFileMap)
1035 {
1036     var sourceFiles = [];
1037     for (var url in sourceFileMap)
1038         sourceFiles.push(sourceFileMap[url]);
1039 
1040     if (FBTrace.DBG_SOURCEFILES)
1041         FBTrace.sysout("sourceFilesAsArray sourcefiles="+sourceFiles.length, sourceFiles);
1042 
1043     return sourceFiles;
1044 };
1045 
1046 Firebug.SourceFile.mapAsArray = function(map)
1047 {
1048     var entries = [];
1049     for (var url in map)
1050         entries.push(map[url]);
1051 
1052     return entries;
1053 };
1054 
1055 // ********************************************************************************************* //
1056 
1057 return Firebug.SourceFile;
1058 
1059 // ********************************************************************************************* //
1060 });
1061