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