Reviewed by Darin.
[WebKit-https.git] / WebKitTools / Drosera / debugger.js
1 /*
2  * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 var files = new Array();
30 var filesLookup = new Object();
31 var scripts = new Array();
32 var currentFile = -1;
33 var currentRow = null;
34 var currentStack = null;
35 var currentCallFrame = null;
36 var lastStatement = null;
37 var frameLineNumberStack = new Array();
38 var previousFiles = new Array();
39 var nextFiles = new Array();
40 var isResizingColumn = false;
41 var draggingBreakpoint = null;
42 var steppingOut = false;
43 var steppingOver = false;
44 var steppingStack = 0;
45 var pauseOnNextStatement = false;
46 var pausedWhileLeavingFrame = false;
47 var consoleWindow = null;
48
49 ScriptCallFrame = function (functionName, index, row)
50 {
51     this.functionName = functionName;
52     this.index = index;
53     this.row = row;
54     this.localVariableNames = null;
55 }
56
57 ScriptCallFrame.prototype.valueForScopeVariable = function (name)
58 {
59     return DebuggerDocument.valueForScopeVariableNamed_inCallFrame_(name, this.index);
60 }
61
62 ScriptCallFrame.prototype.loadVariables = function ()
63 {
64     if (!this.localVariableNames)
65         this.localVariableNames = DebuggerDocument.localScopeVariableNamesForCallFrame_(this.index);
66
67     var variablesTable = document.getElementById("variablesTable");
68     variablesTable.innerHTML = "";
69
70     for(var i = 0; i < this.localVariableNames.length; i++) {
71         var tr = document.createElement("tr");
72         var td = document.createElement("td");
73         td.innerText = this.localVariableNames[i];
74         td.className = "variable";
75         tr.appendChild(td);
76
77         td = document.createElement("td");
78         td.innerText = this.valueForScopeVariable(this.localVariableNames[i]);
79         tr.appendChild(td);
80         tr.addEventListener("click", selectVariable, true);
81
82         variablesTable.appendChild(tr);
83     }
84 }
85
86 function sleep(numberMillis) {
87     var now = new Date();
88     var exitTime = now.getTime() + numberMillis;
89     while (true) {
90         now = new Date();
91         if (now.getTime() > exitTime)
92             return;
93     }
94 }
95
96 function headerMouseDown(element) {
97     if (!isResizingColumn) 
98         element.style.background = "url(glossyHeaderPressed.png) repeat-x";
99 }
100
101 function headerMouseUp(element) {
102     element.style.background = "url(glossyHeader.png) repeat-x";
103 }
104
105 function headerMouseOut(element) {
106     element.style.background = "url(glossyHeader.png) repeat-x";
107 }
108
109 function dividerDragStart(element, dividerDrag, dividerDragEnd, event, cursor) {
110     element.dragging = true;
111     element.dragLastY = event.clientY + window.scrollY;
112     element.dragLastX = event.clientX + window.scrollX;
113     document.addEventListener("mousemove", dividerDrag, true);
114     document.addEventListener("mouseup", dividerDragEnd, true);
115     document.body.style.cursor = cursor;
116     event.preventDefault();
117 }
118
119 function sourceDividerDragStart(event) {
120     dividerDragStart(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event, "row-resize");
121 }
122
123 function infoDividerDragStart(event) {
124     dividerDragStart(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event, "col-resize");
125 }
126
127 function columnResizerDragStart(event) {
128     isResizingColumn = true;
129     dividerDragStart(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event, "col-resize");
130 }
131
132 function columnResizerDragEnd(event) {
133     isResizingColumn = false;
134     dividerDragEnd(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
135 }
136
137 function infoDividerDragEnd(event) {
138     dividerDragEnd(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event);
139 }
140
141 function sourceDividerDragEnd(event) {
142     dividerDragEnd(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event);
143 }
144
145 function dividerDragEnd(element, dividerDrag, dividerDragEnd, event) {
146     element.dragging = false;
147     document.removeEventListener("mousemove", dividerDrag, true);
148     document.removeEventListener("mouseup", dividerDragEnd, true);
149     document.body.style.cursor = null;
150 }
151
152 function columnResizerDrag(event) {
153     var element = document.getElementById("variableColumnResizer");
154     if (element.dragging == true) {
155         var main = document.getElementById("rightPane");
156         var variableColumn = document.getElementById("variable");
157         var rules = document.defaultView.getMatchedCSSRules(variableColumn, "");
158         for (var i = 0; i < rules.length; i++) {
159             if (rules[i].selectorText == ".variable") {
160                 var columnRule = rules[i];
161                 break;
162             }
163         }
164
165         var x = event.clientX + window.scrollX;
166         var delta = element.dragLastX - x;
167         var newWidth = constrainedWidthFromElement(variableColumn.clientWidth - delta, main);
168         if ((variableColumn.clientWidth - delta) == newWidth) // the width wasn't constrained
169             element.dragLastX = x;
170         columnRule.style.width = newWidth + "px";
171         element.style.left = newWidth + "px";
172         event.preventDefault();
173     }
174 }
175
176 function constrainedWidthFromElement(width, element) {
177     if (width < element.clientWidth * 0.25)
178         width = element.clientWidth * 0.25;
179     else if (width > element.clientWidth * 0.75)
180         width = element.clientWidth * 0.75;
181     return width;
182 }
183
184 function constrainedHeightFromElement(height, element) {
185     if (height < element.clientHeight * 0.25)
186         height = element.clientHeight * 0.25;
187     else if (height > element.clientHeight * 0.75)
188         height = element.clientHeight * 0.75;
189     return height;
190 }
191
192 function infoDividerDrag(event) {
193     var element = document.getElementById("infoDivider");
194     if (document.getElementById("infoDivider").dragging == true) {
195         var main = document.getElementById("main");
196         var leftPane = document.getElementById("leftPane");
197         var rightPane = document.getElementById("rightPane");
198         var x = event.clientX + window.scrollX;
199         var delta = element.dragLastX - x;
200         var newWidth = constrainedWidthFromElement(leftPane.clientWidth - delta, main);
201         if ((leftPane.clientWidth - delta) == newWidth) // the width wasn't constrained
202             element.dragLastX = x;
203         leftPane.style.width = newWidth + "px";
204         rightPane.style.left = newWidth + "px";
205         event.preventDefault();
206     }
207 }
208
209 function dividerDrag(event) {
210     var element = document.getElementById("divider");
211     if (document.getElementById("divider").dragging == true) {
212         var main = document.getElementById("main");
213         var top = document.getElementById("info");
214         var bottom = document.getElementById("body");
215         var y = event.clientY + window.scrollY;
216         var delta = element.dragLastY - y;
217         var newHeight = constrainedHeightFromElement(top.clientHeight - delta, main);
218         if ((top.clientHeight - delta) == newHeight) // the height wasn't constrained
219             element.dragLastY = y;
220         top.style.height = newHeight + "px";
221         bottom.style.top = newHeight + "px";
222         event.preventDefault();
223     }
224 }
225
226 function loaded() {
227     document.getElementById("divider").addEventListener("mousedown", sourceDividerDragStart, false);
228     document.getElementById("infoDivider").addEventListener("mousedown", infoDividerDragStart, false);
229     document.getElementById("variableColumnResizer").addEventListener("mousedown", columnResizerDragStart, false);
230 }
231
232 function isPaused() {
233     return DebuggerDocument.isPaused();
234 }
235
236 function pause() {
237     DebuggerDocument.pause();
238 }
239
240 function resume()
241 {
242     if (currentRow) {
243         removeStyleClass(currentRow, "current");
244         currentRow = null;
245     }
246
247     var stackframeTable = document.getElementById("stackframeTable");
248     stackframeTable.innerHTML = ""; // clear the content
249     var variablesTable = document.getElementById("variablesTable");
250     variablesTable.innerHTML = ""; // clear the content
251     currentStack = null;
252     currentCallFrame = null;
253
254     pauseOnNextStatement = false;
255     pausedWhileLeavingFrame = false;
256     steppingOut = false;
257     steppingOver = false;
258     steppingStack = 0;
259
260     DebuggerDocument.resume();
261 }
262
263 function stepInto()
264 {
265     pauseOnNextStatement = false;
266     steppingOut = false;
267     steppingOver = false;
268     steppingStack = 0;
269     DebuggerDocument.stepInto();
270 }
271
272 function stepOver()
273 {
274     pauseOnNextStatement = false;
275     steppingOver = true;
276     steppingStack = 0;
277     DebuggerDocument.resume();
278 }
279
280 function stepOut()
281 {
282     pauseOnNextStatement = pausedWhileLeavingFrame;
283     steppingOver = false;
284     steppingStack = 0;
285     steppingOut = true;
286     DebuggerDocument.resume();
287 }
288
289 function hasStyleClass(element, className)
290 {
291     return ( element.className.indexOf(className) != -1 );
292 }
293
294 function addStyleClass(element, className)
295 {
296     if (!hasStyleClass(element, className))
297         element.className += (element.className.length ? " " + className : className);
298 }
299
300 function removeStyleClass(element, className)
301 {
302     if (hasStyleClass(element, className))
303         element.className = element.className.replace(className, "");
304 }
305
306 function addBreakPoint(event)
307 {
308     var row = event.target.parentNode;
309     if (hasStyleClass(row, "breakpoint")) {
310         if (hasStyleClass(row, "disabled")) {
311             removeStyleClass(row, "disabled");
312             files[currentFile].breakpoints[parseInt(event.target.title)] = 1;
313         } else {
314             addStyleClass(row, "disabled");
315             files[currentFile].breakpoints[parseInt(event.target.title)] = -1;
316         }
317     } else {
318         addStyleClass(row, "breakpoint");
319         removeStyleClass(row, "disabled");
320         files[currentFile].breakpoints[parseInt(event.target.title)] = 1;
321     }
322 }
323
324 function moveBreakPoint(event)
325 {
326     if (hasStyleClass(event.target.parentNode, "breakpoint")) {
327         files[currentFile].breakpoints[parseInt(event.target.title)] = 0;
328         draggingBreakpoint = event.target;
329         draggingBreakpoint.started = false;
330         draggingBreakpoint.dragLastY = event.clientY + window.scrollY;
331         draggingBreakpoint.dragLastX = event.clientX + window.scrollX;
332         var sourcesDocument = document.getElementById("sources").contentDocument;
333         sourcesDocument.addEventListener("mousemove", breakpointDrag, true);
334         sourcesDocument.addEventListener("mouseup", breakpointDragEnd, true);
335         sourcesDocument.body.style.cursor = "default";
336     }
337 }
338
339 function breakpointDrag(event)
340 {
341     var sourcesDocument = document.getElementById("sources").contentDocument;
342     if (!draggingBreakpoint) {
343         sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
344         sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
345         sourcesDocument.body.style.cursor = null;
346         return;
347     }
348
349     var x = event.clientX + window.scrollX;
350     var y = event.clientY + window.scrollY;
351     var deltaX = draggingBreakpoint.dragLastX - x;
352     var deltaY = draggingBreakpoint.dragLastY - y;
353     if (draggingBreakpoint.started || deltaX > 4 || deltaY > 4 || deltaX < -4 || deltaY < -4) {
354         if (!draggingBreakpoint.started) {
355             draggingBreakpoint.isDisabled = hasStyleClass(draggingBreakpoint.parentNode, "disabled");
356             removeStyleClass(draggingBreakpoint.parentNode, "breakpoint");
357             removeStyleClass(draggingBreakpoint.parentNode, "disabled");
358             draggingBreakpoint.started = true;
359
360             var dragImage = sourcesDocument.createElement("img");
361             if (draggingBreakpoint.isDisabled)
362                 dragImage.src = "breakPointDisabled.tif";
363             else
364                 dragImage.src = "breakPoint.tif";
365             dragImage.id = "breakpointDrag";
366             dragImage.style.top = y - 8 + "px";
367             dragImage.style.left = x - 12 + "px";
368             sourcesDocument.body.appendChild(dragImage);
369         } else {
370             var dragImage = sourcesDocument.getElementById("breakpointDrag");
371             if (!dragImage) {
372                 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
373                 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
374                 sourcesDocument.body.style.cursor = null;
375                 return;
376             }
377
378             dragImage.style.top = y - 8 + "px";
379             dragImage.style.left = x - 12 + "px";
380             if (x > 40)
381                 dragImage.style.visibility = "hidden";
382             else
383                 dragImage.style.visibility = null;
384         }
385
386         draggingBreakpoint.dragLastX = x;
387         draggingBreakpoint.dragLastY = y;
388     }
389 }
390
391 function breakpointDragEnd(event)
392 {
393     var sourcesDocument = document.getElementById("sources").contentDocument;
394     sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
395     sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
396     sourcesDocument.body.style.cursor = null;
397
398     var dragImage = sourcesDocument.getElementById("breakpointDrag");
399     if (!dragImage)
400         return;
401
402     dragImage.parentNode.removeChild(dragImage);
403
404     var x = event.clientX + window.scrollX;
405     if (x > 40 || !draggingBreakpoint)
406         return;
407
408     var y = event.clientY + window.scrollY;
409     var rowHeight = draggingBreakpoint.parentNode.offsetHeight;
410     var row = Math.ceil(y / rowHeight);
411     if (row <= 0)
412         row = 1;
413
414     var file = files[currentFile];
415     var table = file.element.firstChild;
416     if (row > table.childNodes.length)
417         return;
418
419     var tr = table.childNodes.item(row - 1);
420     if (!tr)
421         return;
422
423     if (draggingBreakpoint.isDisabled)
424         addStyleClass(tr, "disabled");
425     addStyleClass(tr, "breakpoint");
426     file.breakpoints[row] = (draggingBreakpoint.isDisabled ? -1 : 1);
427
428     draggingBreakpoint = null;
429 }
430
431 function totalOffsetTop(element, stop)
432 {
433     var currentTop = 0;
434     while (element.offsetParent) {
435         currentTop += element.offsetTop
436         element = element.offsetParent;
437         if (element == stop)
438             break;
439     }
440     return currentTop;
441 }
442
443 function switchFile()
444 {
445     var filesSelect = document.getElementById("files");
446     loadFile(filesSelect.options[filesSelect.selectedIndex].value, true);
447 }
448
449 function syntaxHighlight(code)
450 {
451     var keywords = { 'abstract': 1, 'boolean': 1, 'break': 1, 'byte': 1, 'case': 1, 'catch': 1, 'char': 1, 'class': 1, 'const': 1, 'continue': 1, 'debugger': 1, 'default': 1, 'delete': 1, 'do': 1, 'double': 1, 'else': 1, 'enum': 1, 'export': 1, 'extends': 1, 'false': 1, 'final': 1, 'finally': 1, 'float': 1, 'for': 1, 'function': 1, 'goto': 1, 'if': 1, 'implements': 1, 'import': 1, 'in': 1, 'instanceof': 1, 'int': 1, 'interface': 1, 'long': 1, 'native': 1, 'new': 1, 'null': 1, 'package': 1, 'private': 1, 'protected': 1, 'public': 1, 'return': 1, 'short': 1, 'static': 1, 'super': 1, 'switch': 1, 'synchronized': 1, 'this': 1, 'throw': 1, 'throws': 1, 'transient': 1, 'true': 1, 'try': 1, 'typeof': 1, 'var': 1, 'void': 1, 'volatile': 1, 'while': 1, 'with': 1 };
452
453     function echoChar(c) {
454         if (c == '<')
455             result += '&lt;';
456         else if (c == '>')
457             result += '&gt;';
458         else if (c == '&')
459             result += '&amp;';
460         else if (c == '\t')
461             result += '    ';
462         else
463             result += c;
464     }
465
466     function isDigit(number) {
467         var string = "1234567890";
468         if (string.indexOf(number) != -1)
469             return true;
470         return false;
471     }
472
473     function isHex(hex) {
474         var string = "1234567890abcdefABCDEF";
475         if (string.indexOf(hex) != -1)
476             return true;
477         return false;
478     }
479
480     function isLetter(letter) {
481         var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
482         if (string.indexOf(letter) != -1)
483             return true;
484         return false;
485     }
486
487     var result = "";
488     var cPrev = "";
489     var c = "";
490     var cNext = "";
491     for (var i = 0; i < code.length; i++) {
492         cPrev = c;
493         c = code.charAt(i);
494         cNext = code.charAt(i + 1);
495
496         if (c == "/" && cNext == "*") {
497             result += "<span class=\"comment\">";
498             echoChar(c);
499             echoChar(cNext);
500             for (i += 2; i < code.length; i++) {
501                 c = code.charAt(i);
502                 if (c == "\n")
503                     result += "</span>";
504                 echoChar(c);
505                 if (c == "\n")
506                     result += "<span class=\"comment\">";
507                 if (cPrev == "*" && c == "/")
508                     break;
509                 cPrev = c;
510             }
511             result += "</span>";
512             continue;
513         } else if (c == "/" && cNext == "/") {
514             result += "<span class=\"comment\">";
515             echoChar(c);
516             echoChar(cNext);
517             for (i += 2; i < code.length; i++) {
518                 c = code.charAt(i);
519                 if (c == "\n")
520                     break;
521                 echoChar(c);
522             }
523             result += "</span>";
524             echoChar(c);
525             continue;
526         } else if (c == "\"" || c == "'") {
527             var instringtype = c;
528             var stringstart = i;
529             result += "<span class=\"string\">";
530             echoChar(c);
531             for (i += 1; i < code.length; i++) {
532                 c = code.charAt(i);
533                 if (stringstart < (i - 1) && cPrev == instringtype && code.charAt(i - 2) != "\\")
534                     break;
535                 echoChar(c);
536                 cPrev = c;
537             }
538             result += "</span>";
539             echoChar(c);
540             continue;
541         } else if (c == "0" && cNext == "x" && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
542             result += "<span class=\"number\">";
543             echoChar(c);
544             echoChar(cNext);
545             for (i += 2; i < code.length; i++) {
546                 c = code.charAt(i);
547                 if (!isHex(c))
548                     break;
549                 echoChar(c);
550             }
551             result += "</span>";
552             echoChar(c);
553             continue;
554         } else if ((isDigit(c) || ((c == "-" || c == ".") && isDigit(cNext))) && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
555             result += "<span class=\"number\">";
556             echoChar(c);
557             for (i += 1; i < code.length; i++) {
558                 c = code.charAt(i);
559                 if (!isDigit(c) && c != ".")
560                     break;
561                 echoChar(c);
562             }
563             result += "</span>";
564             echoChar(c);
565             continue;
566         } else if(isLetter(c) && (i == 0 || !isLetter(cPrev))) {
567             var keyword = c;
568             var cj = "";
569             for (var j = i + 1; j < i + 12 && j < code.length; j++) {
570                 cj = code.charAt(j);
571                 if (!isLetter(cj))
572                     break;
573                 keyword += cj;
574             }
575
576             if (keywords[keyword]) {
577                 result += "<span class=\"keyword\">" + keyword + "</span>";
578                 i += keyword.length - 1;
579                 continue;
580             }
581         }
582
583         echoChar(c);
584     }
585
586     return result;
587 }
588
589 function navFilePrevious(element)
590 {
591     if (element.disabled)
592         return;
593     var lastFile = previousFiles.pop();
594     if (currentFile != -1)
595         nextFiles.unshift(currentFile);
596     loadFile(lastFile, false);
597 }
598
599 function navFileNext(element)
600 {
601     if (element.disabled)
602         return;
603     var lastFile = nextFiles.shift();
604     if (currentFile != -1)
605         previousFiles.push(currentFile);
606     loadFile(lastFile, false);
607 }
608
609 function updateFunctionStack()
610 {
611     var stackframeTable = document.getElementById("stackframeTable");
612     stackframeTable.innerHTML = ""; // clear the content
613
614     currentStack = new Array();
615     var stack = DebuggerDocument.currentFunctionStack();
616     for(var i = 0; i < stack.length; i++) {
617         var tr = document.createElement("tr");
618         var td = document.createElement("td");
619         td.className = "stackNumber";
620         td.innerText = i;
621         tr.appendChild(td);
622
623         td = document.createElement("td");
624         td.innerText = stack[i];
625         tr.appendChild(td);
626         tr.addEventListener("click", selectStackFrame, true);
627
628         stackframeTable.appendChild(tr);
629
630         var frame = new ScriptCallFrame(stack[i], i, tr);
631         tr.callFrame = frame;
632         currentStack.push(frame);
633
634         if (i == 0) {
635             addStyleClass(tr, "current");
636             frame.loadVariables();
637             currentCallFrame = frame;
638         }
639     }
640 }
641
642 function selectStackFrame(event)
643 {
644     var stackframeTable = document.getElementById("stackframeTable");
645     var rows = stackframeTable.childNodes;
646     for (var i = 0; i < rows.length; i++)
647         removeStyleClass(rows[i], "current");
648     addStyleClass(this, "current");
649     this.callFrame.loadVariables();
650     currentCallFrame = this.callFrame;
651
652     if (frameLineNumberInfo = frameLineNumberStack[this.callFrame.index - 1])
653         jumpToLine(frameLineNumberInfo[0], frameLineNumberInfo[1]);
654     else if (this.callFrame.index == 0)
655         jumpToLine(lastStatement[0], lastStatement[1]);
656 }
657
658 function selectVariable(event)
659 {
660     var variablesTable = document.getElementById("variablesTable");
661     var rows = variablesTable.childNodes;
662     for (var i = 0; i < rows.length; i++)
663         removeStyleClass(rows[i], "current");
664     addStyleClass(this, "current");
665 }
666
667 function loadFile(fileIndex, manageNavLists)
668 {
669     var file = files[fileIndex];
670     if (!file)
671         return;
672
673     if (currentFile != -1 && files[currentFile] && files[currentFile].element)
674         files[currentFile].element.style.display = "none";
675
676     if (!file.loaded) {
677         file.source = file.source.replace(/\r\n|\r/, "\n"); // normalize line endings
678
679         var sourcesDocument = document.getElementById("sources").contentDocument;
680         var sourcesDiv = sourcesDocument.body;
681         var sourceDiv = sourcesDocument.createElement("div");
682         sourceDiv.id = "file" + fileIndex;
683         sourcesDiv.appendChild(sourceDiv);
684         file.element = sourceDiv;
685
686         var table = sourcesDocument.createElement("table");
687         sourceDiv.appendChild(table);
688
689         var lines = syntaxHighlight(file.source).split("\n");
690         for( var i = 0; i < lines.length; i++ ) {
691             var tr = sourcesDocument.createElement("tr");
692             var td = sourcesDocument.createElement("td");
693             td.className = "gutter";
694             td.title = (i + 1);
695             td.addEventListener("click", addBreakPoint, true);
696             td.addEventListener("mousedown", moveBreakPoint, true);
697             tr.appendChild(td);
698
699             td = sourcesDocument.createElement("td");
700             td.className = "source";
701             td.innerHTML = (lines[i].length ? lines[i] : "&nbsp;");
702             tr.appendChild(td);
703             table.appendChild(tr);
704         }
705
706         file.loaded = true;
707     }
708
709     file.element.style.display = null;
710
711     document.getElementById("filesPopupButtonContent").innerText = (file.url ? file.url : "(unknown script)");
712     
713     var filesSelect = document.getElementById("files");
714     for (var i = 0; i < filesSelect.childNodes.length; i++) {
715         if (filesSelect.childNodes[i].value == fileIndex) {
716             filesSelect.selectedIndex = i;
717             break;
718         }
719     }
720
721     if (manageNavLists) {
722         nextFiles = new Array();
723         if (currentFile != -1)
724             previousFiles.push(currentFile);
725     }
726
727     document.getElementById("navFileLeftButton").disabled = (previousFiles.length == 0);
728     document.getElementById("navFileRightButton").disabled = (nextFiles.length == 0);
729
730     currentFile = fileIndex;
731 }
732
733 function updateFileSource(source, url, force)
734 {
735     var fileIndex = filesLookup[url];
736     if (!fileIndex || !source.length)
737         return;
738
739     var file = files[fileIndex];
740     if (force || file.source.length != source.length || file.source != source) {
741         file.source = source;
742         file.loaded = false;
743
744         if (file.element) {
745             file.element.parentNode.removeChild(file.element);
746             file.element = null;
747         }
748
749         if (currentFile == fileIndex)
750             loadFile(fileIndex, false);
751     }
752 }
753
754 function didParseScript(source, fileSource, url, sourceId, baseLineNumber)
755 {
756     var fileIndex = filesLookup[url];
757     var file = files[fileIndex];
758     var firstLoad = false;
759     if (!fileIndex || !file) {
760         fileIndex = files.length + 1;
761         if (url.length)
762             filesLookup[url] = fileIndex;
763
764         file = new Object();
765         file.scripts = new Array();
766         file.breakpoints = new Array();
767         file.source = (fileSource.length ? fileSource : source);
768         file.url = (url.length ? url : null);
769         file.loaded = false;
770
771         files[fileIndex] = file;
772
773         var filesSelect = document.getElementById("files");
774         var option = document.createElement("option");
775         files[fileIndex].menuOption = option;
776         option.value = fileIndex;
777         option.text = (file.url ? file.url : "(unknown script)");
778         filesSelect.appendChild(option);
779         firstLoad = true;
780     }
781
782     var sourceObj = new Object();
783     sourceObj.file = fileIndex;
784     sourceObj.baseLineNumber = baseLineNumber;
785     file.scripts.push(sourceId);
786     scripts[sourceId] = sourceObj;
787
788     if (!firstLoad)
789         updateFileSource((fileSource.length ? fileSource : source), url, false);
790
791     if (currentFile == -1)
792         loadFile(fileIndex, true);
793 }
794
795 function jumpToLine(sourceId, line)
796 {
797     var script = scripts[sourceId];
798     if (line <= 0 || !script)
799         return;
800
801     var file = files[script.file];
802     if (!file)
803         return;
804
805     if (currentFile != script.file)
806         loadFile(script.file, true);
807     if (currentRow)
808         removeStyleClass(currentRow, "current");
809     if (!file.element)
810         return;
811     if (line > file.element.firstChild.childNodes.length)
812         return;
813
814     currentRow = file.element.firstChild.childNodes.item(line - 1);
815     if (!currentRow)
816         return;
817
818     addStyleClass(currentRow, "current");
819
820     var sourcesDiv = document.getElementById("sources");
821     var sourcesDocument = document.getElementById("sources").contentDocument;
822     var parent = sourcesDocument.body;
823     var offset = totalOffsetTop(currentRow, parent);
824     if (offset < (parent.scrollTop + 20) || offset > (parent.scrollTop + sourcesDiv.clientHeight - 20))
825         parent.scrollTop = totalOffsetTop(currentRow, parent) - (sourcesDiv.clientHeight / 2) + 10;
826 }
827
828 function willExecuteStatement(sourceId, line, fromLeavingFrame)
829 {
830     var script = scripts[sourceId];
831     if (line <= 0 || !script)
832         return;
833
834     var file = files[script.file];
835     if (!file)
836         return;
837
838     lastStatement = [sourceId, line];
839
840     if (pauseOnNextStatement || file.breakpoints[line] == 1 || (steppingOver && !steppingStack)) {
841         pause();
842         pauseOnNextStatement = false;
843         pausedWhileLeavingFrame = fromLeavingFrame || false;
844     }
845
846     if (isPaused()) {
847         updateFunctionStack();
848         jumpToLine(sourceId, line);
849     }
850 }
851
852 function didEnterCallFrame(sourceId, line)
853 {
854     if (steppingOver || steppingOut)
855         steppingStack++;
856
857     if (lastStatement)
858         frameLineNumberStack.unshift(lastStatement);
859     willExecuteStatement(sourceId, line);
860 }
861
862 function willLeaveCallFrame(sourceId, line)
863 {
864     if (line <= 0)
865         resume();
866     willExecuteStatement(sourceId, line, true);
867     frameLineNumberStack.shift();
868     if (!steppingStack)
869         steppingOver = false;
870     if (steppingOut && !steppingStack) {
871         steppingOut = false;
872         pauseOnNextStatement = true;
873     }
874     if ((steppingOver || steppingOut) && steppingStack >= 1)
875         steppingStack--;
876 }
877
878 function exceptionWasRaised(sourceId, line)
879 {
880     pause();
881     updateFunctionStack();
882     jumpToLine(sourceId, line);
883 }
884
885 function showConsoleWindow()
886 {
887     if (!consoleWindow)
888         consoleWindow = window.open("console.html", "console", "top=200, left=200, width=500, height=300, toolbar=yes, resizable=yes");
889 }