1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/trace", 5 "firebug/lib/url", 6 "firebug/lib/locale", 7 "firebug/lib/wrapper", 8 "firebug/js/sourceLink", 9 "firebug/lib/deprecated", 10 "firebug/lib/options", 11 ], 12 function (FBTrace, Url, Locale, Wrapper, SourceLink, Deprecated, Options) { 13 14 // ********************************************************************************************* // 15 // Constants 16 17 // ********************************************************************************************* // 18 // Implementation 19 20 var StackFrame = {}; 21 22 StackFrame.getStackTrace = Deprecated.deprecated("name change for self-documentation", 23 StackFrame.getCorrectedStackTrace); 24 25 /** 26 * Converts a Mozilla stack frame to a frameXB 27 */ 28 StackFrame.getCorrectedStackTrace = function(frame, context) 29 { 30 try 31 { 32 var trace = new StackFrame.StackTrace(); 33 var newestFrame = null; 34 var nextOlderFrame = null; 35 for (; frame && frame.isValid; frame = frame.callingFrame) 36 { 37 if (!(Options.get("filterSystemURLs") && 38 Url.isSystemURL(Url.normalizeURL(frame.script.fileName)))) 39 { 40 var stackFrame = StackFrame.getStackFrame(frame, context, newestFrame); 41 if (stackFrame) 42 { 43 if (!newestFrame) 44 newestFrame = stackFrame; 45 46 if (context.currentFrame && context.currentFrame === frame) 47 trace.currentFrameIndex = trace.length; 48 49 stackFrame.setCallingFrame(nextOlderFrame, trace.frames.length); 50 nextOlderFrame = stackFrame; 51 trace.frames.push(stackFrame); 52 } 53 } 54 else 55 { 56 if (FBTrace.DBG_STACK) 57 FBTrace.sysout("lib.getCorrectedStackTrace isSystemURL frame.script.fileName "+ 58 frame.script.fileName+"\n"); 59 } 60 } 61 62 if (trace.frames.length > 100) // TODO in the loop above 63 { 64 var originalLength = trace.frames.length; 65 trace.frames.splice(50, originalLength - 100, null); 66 var excuse = "(eliding "+(originalLength - 100)+" frames)"; 67 68 trace.frames[50] = new StackFrame.StackFrame({href: excuse}, 0, excuse, 69 [], null, null, context, newestFrame); 70 } 71 72 } 73 catch (exc) 74 { 75 if (FBTrace.DBG_ERRORS) 76 FBTrace.sysout("getCorrectedStackTrace FAILS "+exc, exc); 77 } 78 return trace; 79 }; 80 81 /* 82 * Converts from Mozilla stack frame to frameXB 83 */ 84 StackFrame.getStackFrame = function(frame, context, newestFrameXB) 85 { 86 if (frame.isNative || frame.isDebugger) 87 { 88 var excuse = (frame.isNative) ? "(native)" : "(debugger)"; 89 if (FBTrace.DBG_STACK) 90 FBTrace.sysout("lib.getStackFrame "+excuse+" frame\n"); 91 92 return new StackFrame.StackFrame({href: excuse}, 0, excuse, [], 93 null, null, context, newestFrameXB); 94 } 95 try 96 { 97 var sourceFile = Firebug.SourceFile.getSourceFileByScript(context, frame.script); 98 if (sourceFile) 99 { 100 var url = sourceFile.href; 101 var analyzer = sourceFile.getScriptAnalyzer(frame.script); 102 103 var lineNo = analyzer.getSourceLineFromFrame(context, frame); 104 var fncSpec = analyzer.getFunctionDescription(frame.script, context, frame); 105 if (!fncSpec.name || fncSpec.name === "anonymous") 106 { 107 fncSpec.name = StackFrame.guessFunctionName(url, frame.script.baseLineNumber, context); 108 if (!fncSpec.name) 109 fncSpec.name = "?"; 110 } 111 112 if (FBTrace.DBG_STACK) 113 FBTrace.sysout("lib.getStackFrame "+fncSpec.name, {sourceFile: sourceFile, 114 script: frame.script, fncSpec: fncSpec, analyzer: analyzer}); 115 116 return new StackFrame.StackFrame(sourceFile, lineNo, fncSpec.name, fncSpec.args, frame, 117 frame.pc, sourceFile.context, newestFrameXB); 118 } 119 else 120 { 121 if (FBTrace.DBG_STACK) 122 FBTrace.sysout("lib.getStackFrame NO sourceFile tag@file:"+frame.script.tag+ 123 "@"+frame.script.fileName, frame.script.functionSource); 124 125 var script = frame.script; 126 return new StackFrame.StackFrame({href: Url.normalizeURL(script.fileName)}, frame.line, 127 script.functionName, [], frame, frame.pc, context, newestFrameXB); 128 } 129 } 130 catch (exc) 131 { 132 if (FBTrace.DBG_STACK) 133 FBTrace.sysout("getStackFrame fails: "+exc, exc); 134 return null; 135 } 136 }; 137 138 // ********************************************************************************************* // 139 // frameXB, cross-browser frame 140 141 StackFrame.StackFrame = function(sourceFile, lineNo, functionName, args, nativeFrame, pc, 142 context, newestFrame) 143 { 144 // Essential fields 145 this.sourceFile = sourceFile; 146 this.line = lineNo; 147 148 var fn = StackFrame.getDisplayName(nativeFrame ? nativeFrame.scope : null); 149 this.fn = fn || functionName; // cache? 150 151 this.context = context; 152 153 // the newest frame in the stack containing 'this' frame 154 this.newestFrame = (newestFrame ? newestFrame : this); 155 156 // optional 157 this.args = args; 158 159 // Derived from sourceFile 160 this.href = sourceFile.href; 161 162 // Mozilla 163 this.nativeFrame = nativeFrame; 164 this.pc = pc; 165 this.script = nativeFrame ? nativeFrame.script : null; // TODO-XB 166 }; 167 168 StackFrame.StackFrame.prototype = 169 { 170 getURL: function() 171 { 172 return this.href; 173 }, 174 175 getCompilationUnit: function() 176 { 177 return this.context.getCompilationUnit(this.href); 178 }, 179 180 getStackNewestFrame: function() 181 { 182 return this.newestFrame; 183 }, 184 185 getFunctionName: function() 186 { 187 return this.fn; 188 }, 189 190 toSourceLink: function() 191 { 192 return new SourceLink.SourceLink(this.sourceFile.href, this.line, "js"); 193 }, 194 195 toString: function() 196 { 197 return this.fn+", "+this.sourceFile.href+"@"+this.line; 198 }, 199 200 setCallingFrame: function(caller, frameIndex) 201 { 202 this.callingFrame = caller; 203 this.frameIndex = frameIndex; 204 }, 205 206 getCallingFrame: function() 207 { 208 if (FBTrace.DBG_STACK) 209 FBTrace.sysout("getCallingFrame "+this, this); 210 211 if (!this.callingFrame && this.nativeFrame && this.nativeFrame.isValid) 212 { 213 var nativeCallingFrame = this.nativeFrame.callingFrame; 214 if (nativeCallingFrame) 215 this.callingFrame = StackFrame.getStackFrame(nativeCallingFrame, this.context, 216 this.newestFrame); 217 } 218 return this.callingFrame; 219 }, 220 221 getFrameIndex: function() 222 { 223 return this.frameIndex; 224 }, 225 226 getLineNumber: function() 227 { 228 return this.line; 229 }, 230 231 destroy: function() 232 { 233 if (FBTrace.DBG_STACK) 234 FBTrace.sysout("StackFrame destroyed:"+this.uid+"\n"); 235 236 this.script = null; 237 this.nativeFrame = null; 238 this.context = null; 239 }, 240 241 signature: function() 242 { 243 return this.script.tag + "." + this.pc; 244 }, 245 246 getThisValue: function() 247 { 248 if (this.nativeFrame && !this.thisVar) 249 this.thisVar = Wrapper.unwrapIValue(this.nativeFrame.thisValue, Firebug.viewChrome); 250 return this.thisVar; 251 }, 252 253 getScopes: function(viewChrome) 254 { 255 if (this.nativeFrame && !this.scope) 256 this.scope = this.generateScopeChain(this.nativeFrame.scope, viewChrome); 257 return this.scope; 258 }, 259 260 clearScopes: function(viewChrome) 261 { 262 // Clears cached scope chain, so that it regenerates the next time 263 // getScopes() is executed. 264 this.scope = null; 265 }, 266 267 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 268 // Private 269 270 generateScopeChain: function (scope, viewChrome) 271 { 272 var ret = []; 273 while (scope) 274 { 275 var scopeVars; 276 277 // getWrappedValue will not contain any variables for closure 278 // scopes, so we want to special case this to get all variables 279 // in all cases. 280 if (scope.jsClassName == "Call") 281 { 282 scopeVars = Wrapper.unwrapIValueObject(scope, viewChrome) 283 scopeVars.toString = function() {return Locale.$STR("Closure Scope");} 284 } 285 else if (scope.jsClassName == "Block") 286 { 287 scopeVars = Wrapper.unwrapIValueObject(scope, viewChrome) 288 scopeVars.toString = function() {return Locale.$STR("Block Scope");} 289 } 290 else 291 { 292 scopeVars = Wrapper.unwrapIValue(scope, Firebug.viewChrome); 293 if (scopeVars && scopeVars.hasOwnProperty) 294 { 295 if (!scopeVars.hasOwnProperty("toString")) 296 { 297 (function() { 298 var className = scope.jsClassName; 299 scopeVars.toString = function() 300 { 301 return Locale.$STR(className + " Scope"); 302 }; 303 })(); 304 } 305 } 306 else 307 { 308 // do not trace scopeVars, you will get a uncatchable exception 309 if (FBTrace.DBG_ERRORS) 310 FBTrace.sysout("dom .generateScopeChain: bad scopeVars for " + 311 "scope.jsClassName:" + scope.jsClassName); 312 313 scopeVars = {error: "Mozilla error: invalid scope variables"}; 314 } 315 } 316 317 if (scopeVars) 318 ret.push(scopeVars); 319 320 scope = scope.jsParent; 321 } 322 323 ret.toString = function() 324 { 325 return Locale.$STR("Scope Chain"); 326 }; 327 328 return ret; 329 }, 330 }; 331 332 //-----------------------111111----222222-----33---444 1 All 'Not a (' followed by (; 2 All 'Not a )' followed by a ); 3 text between @ and : digits 333 334 var reErrorStackLine = /^(.*)@(.*):(\d*)$/; 335 var reErrorStackLine2 = /^([^\(]*)\((.*)\)$/; 336 337 StackFrame.parseToStackFrame = function(line, context) // function name (arg, arg, arg)@fileName:lineNo 338 { 339 var last255 = line.length - 255; 340 if (last255 > 0) 341 line = line.substr(last255); // avoid regexp on monster compressed source (issue 4135) 342 343 var m = reErrorStackLine.exec(line); 344 if (m) 345 { 346 var m2 = reErrorStackLine2.exec(m[1]); 347 if (m2) 348 { 349 var params = m2[2].split(','); 350 //FBTrace.sysout("parseToStackFrame",{line:line,paramStr:m2[2],params:params}); 351 //var params = JSON.parse("["+m2[2]+"]"); 352 return new StackFrame.StackFrame({href:m[2]}, m[3], m2[1], params, null, null, context); 353 } 354 else 355 { 356 // Firefox 14 removes arguments from <exception-object>.stack.toString() 357 // That's why the m2 reg doesn't match 358 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=744842 359 return new StackFrame.StackFrame({href:m[2]}, m[3], m[1], [], null, null, context); 360 } 361 } 362 } 363 364 StackFrame.parseToStackTrace = function(stack, context) 365 { 366 var lines = stack.split('\n'); 367 var trace = new StackFrame.StackTrace(); 368 for (var i = 0; i < lines.length; i++) 369 { 370 var frame = StackFrame.parseToStackFrame(lines[i],context); 371 372 if (FBTrace.DBG_STACK) 373 FBTrace.sysout("parseToStackTrace i "+i+" line:"+lines[i]+ "->frame: "+frame, frame); 374 375 if (frame) 376 trace.frames.push(frame); 377 } 378 return trace; 379 } 380 381 StackFrame.cleanStackTraceOfFirebug = function(trace) 382 { 383 if (trace && trace.frames) 384 { 385 while (trace.frames.length && 386 ( 387 /^_[fF]irebug/.test(trace.frames[trace.frames.length - 1].fn) || 388 /^\s*with\s*\(\s*_[fF]irebug/.test(trace.frames[trace.frames.length - 1].sourceFile.source) 389 ) 390 ) 391 { 392 trace.frames.pop(); 393 } 394 if (trace.frames.length == 0) 395 trace = undefined; 396 } 397 return trace; 398 } 399 400 StackFrame.getStackDump = function() 401 { 402 var lines = []; 403 for (var frame = Components.stack; frame; frame = frame.caller) 404 lines.push(frame.filename + " (" + frame.lineNumber + ")"); 405 406 return lines.join("\n"); 407 }; 408 409 StackFrame.getJSDStackDump = function(newestFrame) 410 { 411 var lines = []; 412 for (var frame = newestFrame; frame; frame = frame.callingFrame) 413 lines.push(frame.script.fileName + " (" + frame.line + ")"); 414 415 return lines.join("\n"); 416 }; 417 418 StackFrame.getStackSourceLink = function() 419 { 420 for (var frame = Components.stack; frame; frame = frame.caller) 421 { 422 if (frame.filename && frame.filename.indexOf("://firebug/") > 0) 423 { 424 for (; frame; frame = frame.caller) 425 { 426 var firebugComponent = "/modules/firebug-"; 427 if (frame.filename && frame.filename.indexOf("://firebug/") < 0 && 428 frame.filename.indexOf(firebugComponent) == -1) 429 break; 430 } 431 break; 432 } 433 } 434 return StackFrame.getFrameSourceLink(frame); 435 } 436 437 StackFrame.getFrameSourceLink = function(frame) 438 { 439 if (frame && frame.filename && frame.filename.indexOf("XPCSafeJSObjectWrapper") == -1) 440 return new SourceLink.SourceLink(frame.filename, frame.lineNumber, "js"); 441 else 442 return null; 443 }; 444 445 // TODO delete this, only used by console and console injector. 446 StackFrame.getStackFrameId = function() 447 { 448 for (var frame = Components.stack; frame; frame = frame.caller) 449 { 450 if (frame.languageName == "JavaScript" 451 && !(frame.filename && frame.filename.indexOf("://firebug/") > 0)) 452 { 453 return frame.filename + "/" + frame.lineNumber; 454 } 455 } 456 return null; 457 }; 458 459 // ********************************************************************************************* // 460 461 StackFrame.StackTrace = function(adoptFrames) 462 { 463 this.frames = adoptFrames || []; 464 }; 465 466 StackFrame.StackTrace.prototype = 467 { 468 toString: function() 469 { 470 var trace = "<top>\n"; 471 for (var i = 0; i < this.frames.length; i++) 472 { 473 trace += "[" + i + "]"+ this.frames[i]+"\n"; 474 } 475 trace += "<bottom>\n"; 476 return trace; 477 }, 478 479 reverse: function() 480 { 481 this.frames.reverse(); 482 return this; 483 }, 484 485 destroy: function() 486 { 487 for (var i = 0; i < this.frames.length; i++) 488 this.frames[i].destroy(); 489 490 if (FBTrace.DBG_STACK) 491 FBTrace.sysout("lib.StackTrace destroy "+this.uid+"\n"); 492 }, 493 494 toSourceLink: function() 495 { 496 if (this.frames.length > 0) 497 return this.frames[0]; 498 } 499 }; 500 501 // ********************************************************************************************* // 502 503 StackFrame.traceToString = function(trace) 504 { 505 var str = "<top>"; 506 for(var i = 0; i < trace.frames.length; i++) 507 str += "\n" + trace.frames[i]; 508 str += "\n<bottom>"; 509 return str; 510 }; 511 512 StackFrame.buildStackTrace = function(frame) 513 { 514 var trace = new StackFrame.StackTrace(); 515 while (frame) 516 { 517 trace.frames.push(frame); 518 frame.frameIndex = trace.frames.length; 519 frame = frame.getCallingFrame(); 520 } 521 522 // Set the first frame (the one passed into this function) as the current one (issue 4249). 523 if (trace.frames.length > 0) 524 trace.currentFrameIndex = 1; 525 526 return trace; 527 }; 528 529 // ********************************************************************************************* // 530 531 StackFrame.getFunctionName = function(script, context, frame, noArgs) 532 { 533 if (!script) 534 { 535 if (FBTrace.DBG_STACK) 536 FBTrace.sysout("stackFrame.getFunctionName FAILS typeof(script)="+typeof(script)+"\n"); 537 return "(no script)"; 538 } 539 540 var name = this.getDisplayName(frame ? frame.scope : null, script); 541 if (name) 542 return name; 543 544 name = script.functionName; 545 if (!name || (name == "anonymous")) 546 { 547 name = null; 548 var analyzer = Firebug.SourceFile.getScriptAnalyzer(context, script); 549 if (analyzer && frame) 550 { 551 if (FBTrace.DBG_STACK) 552 FBTrace.sysout("getFunctionName analyzer.sourceFile:", analyzer.sourceFile); 553 554 var functionSpec = analyzer.getFunctionDescription(script, context, frame); 555 if (functionSpec.name) 556 name = functionSpec.name + (noArgs ? "" : "("+functionSpec.args.join(',')+")"); 557 } 558 559 if (!name || name == "anonymous") 560 { 561 if (FBTrace.DBG_STACK) 562 FBTrace.sysout("getFunctionName no analyzer, "+script.baseLineNumber+"@"+ 563 script.fileName+"\n"); 564 name = StackFrame.guessFunctionName(Url.normalizeURL(script.fileName), 565 script.baseLineNumber, context); 566 } 567 } 568 569 if (FBTrace.DBG_STACK) 570 FBTrace.sysout("getFunctionName "+script.tag+" ="+name+"\n"); 571 572 return name; 573 } 574 575 StackFrame.getDisplayName = function(scope, script) 576 { 577 try 578 { 579 if (scope) 580 { 581 return Wrapper.unwrapIValue(scope).arguments.callee.displayName; 582 } 583 else if (script) 584 { 585 var fnObj = Wrapper.unwrapIValue(script.functionObject); 586 return (fnObj && fnObj.displayName) ? fnObj.displayName : script.functionName; 587 } 588 } 589 catch (err) 590 { 591 if (FBTrace.DBG_STACK) 592 FBTrace.sysout("stackFrame.getDisplayName; EXCEPTION " + err, err); 593 } 594 } 595 596 StackFrame.guessFunctionName = function(url, lineNo, context) 597 { 598 if (context) 599 { 600 if (context.sourceCache) 601 return StackFrame.guessFunctionNameFromLines(url, lineNo, context.sourceCache); 602 } 603 return "? in "+Url.getFileName(url)+"@"+lineNo; 604 } 605 606 var reGuessFunction = /['"]?([$0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/; 607 var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/; 608 StackFrame.guessFunctionNameFromLines = function(url, lineNo, sourceCache) 609 { 610 // Walk backwards from the first line in the function until we find the line which 611 // matches the pattern above, which is the function definition 612 var line = ""; 613 if (FBTrace.DBG_FUNCTION_NAMES) 614 FBTrace.sysout("getFunctionNameFromLines for line@URL="+lineNo+"@"+url+"\n"); 615 616 for (var i = 0; i < 4; ++i) 617 { 618 line = sourceCache.getLine(url, lineNo-i) + line; 619 if (line != undefined) 620 { 621 var m = reGuessFunction.exec(line); 622 if (m) 623 { 624 return m[1]; 625 } 626 else 627 { 628 if (FBTrace.DBG_FUNCTION_NAMES) 629 FBTrace.sysout("lib.guessFunctionName re failed for lineNo-i="+lineNo+ 630 "-"+i+" line="+line+"\n"); 631 } 632 633 m = reFunctionArgNames.exec(line); 634 if (m && m[1]) 635 return m[1]; 636 } 637 } 638 return "(?)"; 639 } 640 641 // Mozilla 642 StackFrame.getFunctionArgValues = function(frame) 643 { 644 if (frame.isValid && frame.scope.jsClassName == "Call") 645 var values = StackFrame.getArgumentsFromCallScope(frame); 646 else 647 var values = StackFrame.getArgumentsFromObjectScope(frame); 648 649 if (FBTrace.DBG_STACK) 650 FBTrace.sysout("stackFrame.getFunctionArgValues "+frame+" scope: "+frame.scope.jsClassName, 651 {values: values}); 652 653 return values; 654 } 655 656 // Mozilla 657 StackFrame.getArgumentsFromObjectScope = function(frame) 658 { 659 var argNames = frame.script.getParameterNames(); 660 var scope = Wrapper.unwrapIValue(frame.scope, Firebug.viewChrome); 661 662 var values = []; 663 664 for (var i = 0; i < argNames.length; ++i) 665 { 666 var argName = argNames[i]; 667 if (scope) 668 { 669 var pvalue = scope[argName]; 670 //?? XXXjjb why are we unwrapping here, scope is a normal object 671 //var value = pvalue ? Wrapper.unwrapIValue(pvalue.value) : undefined; 672 values.push({name: argName, value: pvalue}); 673 } 674 else 675 { 676 values.push({name: argName}); 677 } 678 } 679 680 return values; 681 }; 682 683 StackFrame.getArgumentsFromCallScope = function(frame) 684 { 685 var argNames = frame.script.getParameterNames(); 686 var scope = frame.scope; 687 var values = []; 688 for (var i = 0; i < argNames.length; ++i) 689 { 690 var argName = argNames[i]; 691 var pvalue = scope.getProperty(argName); // jsdIValue in jsdIDebuggerService 692 var value = pvalue ? Wrapper.unwrapIValue(pvalue.value, Firebug.viewChrome) : undefined; 693 values.push({name: argName, value: value}); 694 } 695 696 return values; 697 }; 698 699 // ********************************************************************************************* // 700 701 var saveShowStackTrace; 702 703 /** 704 * use in the try{} around a call to getInterface to prevent fbs from generating stack traces 705 */ 706 StackFrame.suspendShowStackTrace = function() 707 { 708 saveShowStackTrace = Firebug.showStackTrace; 709 Firebug.showStackTrace = false; 710 }; 711 712 /** 713 * use in the finally{} to undo the suspendShowStackTrace 714 */ 715 StackFrame.resumeShowStackTrace = function() 716 { 717 if (saveShowStackTrace) 718 { 719 Firebug.showStackTrace = saveShowStackTrace; 720 saveShowStackTrace = null; 721 } 722 }; 723 724 // ********************************************************************************************* // 725 // Registration 726 727 return StackFrame; 728 729 // ********************************************************************************************* // 730 }); 731