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