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