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