2006-12-21 Mark Rowe <bdash@webkit.org>
[WebKit-https.git] / WebKitTools / Drosera / debugger.js
1 /*
2  * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
3  * Copyright (C) 2006 David Smith (catfish.man@gmail.com)
4  * Copyright (C) 2006 Vladimir Olexa (vladimir.olexa@gmail.com)
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer. 
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution. 
15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission. 
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 var files = new Array();
32 var filesLookup = new Object();
33 var scripts = new Array();
34 var currentFile = -1;
35 var currentRow = null;
36 var currentStack = null;
37 var currentCallFrame = null;
38 var lastStatement = null;
39 var frameLineNumberStack = new Array();
40 var previousFiles = new Array();
41 var nextFiles = new Array();
42 var isResizingColumn = false;
43 var draggingBreakpoint = null;
44 var steppingOut = false;
45 var steppingOver = false;
46 var steppingStack = 0;
47 var pauseOnNextStatement = false;
48 var pausedWhileLeavingFrame = false;
49 var consoleWindow = null;
50 var breakpointEditorHTML = DebuggerDocument.breakpointEditorHTML();
51 var pendingAction = null;
52
53 ScriptCallFrame = function (functionName, index, row)
54 {
55     this.functionName = functionName;
56     this.index = index;
57     this.row = row;
58     this.localVariableNames = null;
59 }
60
61 ScriptCallFrame.prototype.valueForScopeVariable = function (name)
62 {
63     return DebuggerDocument.valueForScopeVariableNamed_inCallFrame_(name, this.index);
64 }
65
66 ScriptCallFrame.prototype.loadVariables = function ()
67 {
68     if (!this.localVariableNames)
69         this.localVariableNames = DebuggerDocument.localScopeVariableNamesForCallFrame_(this.index);
70
71     var variablesTable = document.getElementById("variablesTable");
72     variablesTable.innerHTML = "";
73
74     for(var i = 0; i < this.localVariableNames.length; i++) {
75         var tr = document.createElement("tr");
76         var td = document.createElement("td");
77         td.innerText = this.localVariableNames[i];
78         td.className = "variable";
79         tr.appendChild(td);
80
81         td = document.createElement("td");
82         td.innerText = this.valueForScopeVariable(this.localVariableNames[i]);
83         tr.appendChild(td);
84         tr.addEventListener("click", selectVariable, true);
85
86         variablesTable.appendChild(tr);
87     }
88 }
89
90 function sleep(numberMillis) 
91 {
92     var now = new Date();
93     var exitTime = now.getTime() + numberMillis;
94     while (true) {
95         now = new Date();
96         if (now.getTime() > exitTime)
97             return;
98     }
99 }
100
101 function headerMouseDown(element) 
102 {
103     if (!isResizingColumn) 
104         element.style.background = "url(glossyHeaderPressed.png) repeat-x";
105 }
106
107 function headerMouseUp(element) 
108 {
109     element.style.background = "url(glossyHeader.png) repeat-x";
110 }
111
112 function headerMouseOut(element) 
113 {
114     element.style.background = "url(glossyHeader.png) repeat-x";
115 }
116
117 function filesDividerDragStart(event) 
118 {
119     dividerDragStart(document.getElementById("filesDivider"), filesDividerDrag, filesDividerDragEnd, event, "col-resize");
120 }
121
122 function filesDividerDragEnd(event) 
123 {
124     dividerDragEnd(document.getElementById("filesDivider"), filesDividerDrag, filesDividerDragEnd, event);
125 }
126
127 function filesDividerDrag(event) 
128 {
129     var element = document.getElementById("filesDivider");
130     if (document.getElementById("filesDivider").dragging == true) {
131         var masterMain = document.getElementById("masterMain");
132         var main = document.getElementById("main");
133         var fileBrowser = document.getElementById("fileBrowser");
134         var x = event.clientX + window.scrollX;
135         var delta = element.dragLastX - x;
136         var newWidth = constrainedWidthFromElement(fileBrowser.clientWidth - delta, masterMain, 0.1, 0.9);
137         if ((fileBrowser.clientWidth - delta) == newWidth) // the width wasn't constrained
138             element.dragLastX = x;
139         fileBrowser.style.width = newWidth + "px";
140         main.style.left = newWidth + "px";
141         event.preventDefault();
142     }
143 }
144
145 function dividerDragStart(element, dividerDrag, dividerDragEnd, event, cursor) 
146 {
147     element.dragging = true;
148     element.dragLastY = event.clientY + window.scrollY;
149     element.dragLastX = event.clientX + window.scrollX;
150     document.addEventListener("mousemove", dividerDrag, true);
151     document.addEventListener("mouseup", dividerDragEnd, true);
152     document.body.style.cursor = cursor;
153     event.preventDefault();
154 }
155
156 function dividerDragEnd(element, dividerDrag, dividerDragEnd, event) 
157 {
158     element.dragging = false;
159     document.removeEventListener("mousemove", dividerDrag, true);
160     document.removeEventListener("mouseup", dividerDragEnd, true);
161     document.body.style.removeProperty("cursor");
162 }
163
164 function dividerDrag(event) 
165 {
166     var element = document.getElementById("divider");
167     if (document.getElementById("divider").dragging == true) {
168         var main = document.getElementById("main");
169         var top = document.getElementById("info");
170         var bottom = document.getElementById("body");
171         var y = event.clientY + window.scrollY;
172         var delta = element.dragLastY - y;
173         var newHeight = constrainedHeightFromElement(top.clientHeight - delta, main);
174         if ((top.clientHeight - delta) == newHeight) // the height wasn't constrained
175             element.dragLastY = y;
176         top.style.height = newHeight + "px";
177         bottom.style.top = newHeight + "px";
178         event.preventDefault();
179     }
180 }
181
182 function sourceDividerDragStart(event) 
183 {
184     dividerDragStart(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event, "row-resize");
185 }
186
187 function sourceDividerDragEnd(event) 
188 {
189     dividerDragEnd(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event);
190 }
191
192 function infoDividerDragStart(event) 
193 {
194     dividerDragStart(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event, "col-resize");
195 }
196
197 function infoDividerDragEnd(event) 
198 {
199     dividerDragEnd(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event);
200 }
201
202 function infoDividerDrag(event) 
203 {
204     var element = document.getElementById("infoDivider");
205     if (document.getElementById("infoDivider").dragging == true) {
206         var main = document.getElementById("main");
207         var leftPane = document.getElementById("leftPane");
208         var rightPane = document.getElementById("rightPane");
209         var x = event.clientX + window.scrollX;
210         var delta = element.dragLastX - x;
211         var newWidth = constrainedWidthFromElement(leftPane.clientWidth - delta, main);
212         if ((leftPane.clientWidth - delta) == newWidth) // the width wasn't constrained
213             element.dragLastX = x;
214         leftPane.style.width = newWidth + "px";
215         rightPane.style.left = newWidth + "px";
216         event.preventDefault();
217     }
218 }
219
220 function columnResizerDragStart(event) 
221 {
222     isResizingColumn = true;
223     dividerDragStart(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event, "col-resize");
224 }
225
226 function columnResizerDragEnd(event) 
227 {
228     isResizingColumn = false;
229     dividerDragEnd(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
230 }
231
232 function columnResizerDrag(event) 
233 {
234     var element = document.getElementById("variableColumnResizer");
235     if (element.dragging == true) {
236         var main = document.getElementById("rightPane");
237         var variableColumn = document.getElementById("variable");
238         var rules = document.defaultView.getMatchedCSSRules(variableColumn, "");
239         for (var i = 0; i < rules.length; i++) {
240             if (rules[i].selectorText == ".variable") {
241                 var columnRule = rules[i];
242                 break;
243             }
244         }
245
246         var x = event.clientX + window.scrollX;
247         var delta = element.dragLastX - x;
248         var newWidth = constrainedWidthFromElement(variableColumn.clientWidth - delta, main);
249         if ((variableColumn.clientWidth - delta) == newWidth) // the width wasn't constrained
250             element.dragLastX = x;
251         columnRule.style.width = newWidth + "px";
252         element.style.left = newWidth + "px";
253         event.preventDefault();
254     }
255 }
256
257 function constrainedWidthFromElement(width, element, constrainLeft, constrainRight) 
258 {
259     if (constrainLeft === undefined) constrainLeft = 0.25;
260     if (constrainRight === undefined) constrainRight = 0.75;
261     
262     if (width < element.clientWidth * constrainLeft)
263         width = element.clientWidth * constrainLeft;
264     else if (width > element.clientWidth * constrainRight)
265         width = element.clientWidth * constrainRight;
266     return width;
267 }
268
269 function constrainedHeightFromElement(height, element) 
270 {
271     if (height < element.clientHeight * 0.25)
272         height = element.clientHeight * 0.25;
273     else if (height > element.clientHeight * 0.75)
274         height = element.clientHeight * 0.75;
275     return height;
276 }
277
278 function loaded() 
279 {
280     document.getElementById("divider").addEventListener("mousedown", sourceDividerDragStart, false);
281     document.getElementById("infoDivider").addEventListener("mousedown", infoDividerDragStart, false);
282     document.getElementById("filesDivider").addEventListener("mousedown", filesDividerDragStart, false);
283     document.getElementById("variableColumnResizer").addEventListener("mousedown", columnResizerDragStart, false);
284 }
285
286 function isPaused() 
287 {
288     return DebuggerDocument.isPaused();
289 }
290
291 function pause() 
292 {
293     DebuggerDocument.pause();
294 }
295
296 function resume()
297 {
298     if (currentRow) {
299         currentRow.removeStyleClass("current");
300         currentRow = null;
301     }
302
303     var stackframeTable = document.getElementById("stackframeTable");
304     stackframeTable.innerHTML = ""; // clear the content
305     var variablesTable = document.getElementById("variablesTable");
306     variablesTable.innerHTML = ""; // clear the content
307     currentStack = null;
308     currentCallFrame = null;
309
310     pauseOnNextStatement = false;
311     pausedWhileLeavingFrame = false;
312     steppingOut = false;
313     steppingOver = false;
314     steppingStack = 0;
315
316     DebuggerDocument.resume();
317 }
318
319 function stepInto()
320 {
321     pauseOnNextStatement = false;
322     steppingOut = false;
323     steppingOver = false;
324     steppingStack = 0;
325     DebuggerDocument.stepInto();
326 }
327
328 function stepOver()
329 {
330     pauseOnNextStatement = false;
331     steppingOver = true;
332     steppingStack = 0;
333     DebuggerDocument.resume();
334 }
335
336 function stepOut()
337 {
338     pauseOnNextStatement = pausedWhileLeavingFrame;
339     steppingOver = false;
340     steppingStack = 0;
341     steppingOut = true;
342     DebuggerDocument.resume();
343 }
344
345 Element.prototype.removeStyleClass = function(className) 
346 {
347     if (this.hasStyleClass(className))
348         this.className = this.className.replace(className, "");
349 }
350
351 Element.prototype.addStyleClass = function(className) 
352 {
353     if (!this.hasStyleClass(className))
354         this.className += (this.className.length ? " " + className : className);
355 }
356
357 Element.prototype.hasStyleClass = function(className) 
358 {
359     return this.className.indexOf(className) != -1;
360 }
361
362 Element.prototype.firstParentWithClass = function(className) 
363 {
364     var node = this.parentNode;
365     while(!node.hasStyleClass(className)) {
366         if (node == document) 
367             return null;
368         node = node.parentNode;
369     }
370     return node;
371 }
372
373 Element.prototype.query = function(query) 
374 {
375     return document.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
376 }
377
378 Element.prototype.removeChildren = function()
379 {
380     while (this.firstChild) 
381         this.removeChild(this.firstChild);        
382 }
383
384 function breakpointAction(event)
385 {
386     var file = files[currentFile];
387     var lineNum = event.target.title;
388     
389     if (file.breakpoints[lineNum]) {
390         if (!pendingAction)
391             pendingAction = setTimeout(toggleBreakpointOnLine, DebuggerDocument.doubleClickMilliseconds(), lineNum);  
392     } else
393         file.breakpoints[lineNum] = new BreakPoint(event.target.parentNode, file, lineNum);
394 }
395
396 BreakPoint = function(row, file, line) 
397 {
398     this.row = row;
399     this.file = file;
400     this.line = line;
401     row.addStyleClass("breakpoint");
402     row.removeStyleClass("disabled");
403     this.value = "break";
404     this.enabled = true;
405     this.editor = null;
406     this.type = 0;
407     this.hitcount = 0;
408 }
409
410 function toggleBreakpointEditorOnLine(lineNum)
411 {
412     if (pendingAction) {
413         clearTimeout(pendingAction);
414         pendingAction = null;
415     }
416     var file = files[currentFile];
417     bp = file.breakpoints[lineNum];
418     if (bp) {
419         var editor = bp.editor;
420         if (!editor) {
421             var sourcesDocument = document.getElementById("sources").contentDocument;
422             editor = sourcesDocument.createElement("div");
423             editor.className = "editor";
424             editor.id = lineNum;
425             editor.innerHTML = breakpointEditorHTML;
426             
427             bp.row.childNodes[1].appendChild(editor);
428             
429             bp.editor = editor;
430             file.breakpoints[lineNum] = bp;
431
432             editor.query('.//input[@class="enable"]').checked = bp.enabled;
433
434             editor.query('.//select[@class="editorDropdown"]').selectedIndex = bp.type;
435             updateBreakpointTypeOnLine(lineNum);
436
437             editor.query('.//span[@class="hitCounter"]').innerText = bp.hitcount;
438                 
439             setConditionFieldText(bp);
440         } else {
441             saveBreakpointOnLine(lineNum);
442             bp.row.childNodes[1].removeChild(editor);
443             bp.editor = null;
444         }
445     }
446 }
447
448 function updateBreakpointTypeOnLine(line)
449 {
450     var breakpoint = files[currentFile].breakpoints[line];
451     var editor = breakpoint.editor;
452     var label = editor.query('.//label[@class="conditionLabel"]');
453     var dropdown = editor.query('.//select[@class="editorDropdown"]');
454     breakpoint.type = dropdown.selectedIndex;
455     switch(breakpoint.type) {
456         case 0:
457             label.innerText = "Condition:";
458             break;
459         case 1:
460             label.innerText = "Expression:";
461             break;
462     }
463 }
464
465 function setConditionFieldText(breakpoint)
466 {
467     var conditionField = breakpoint.editor.query('.//div[@class="condition"]');
468     
469     var functionBody = breakpoint.value;
470     if (!functionBody || functionBody == "break")
471         functionBody = "";
472     else {
473         var startIndex = functionBody.indexOf("return((") + 8;
474         var endIndex = null;
475         if (startIndex != 7) //-1 + 8, yes, that's lame
476             endIndex = functionBody.lastIndexOf("))");
477         else {
478             startIndex = functionBody.indexOf("{") + 1;
479             endIndex = functionBody.lastIndexOf("}"); 
480         }
481         functionBody = functionBody.substring(startIndex, endIndex);
482     }
483     conditionField.innerText = functionBody;
484     conditionField.addEventListener("keyup", new Function("saveBreakpointOnLine(" + breakpoint.line + ");"), false);
485     conditionField.focus();
486 }
487
488 function saveBreakpointOnLine(lineNum)
489 {
490     var file = files[currentFile];
491     var breakpoint = file.breakpoints[lineNum];
492     row = file.element.firstChild.childNodes.item(lineNum - 1);
493     var editor = breakpoint.editor;
494     var body = editor.query('.//div[@class="condition"]').innerText;
495     var actionIndex = editor.query('.//select[@class="editorDropdown"]').selectedIndex;
496     if (body.length == 0)
497         breakpoint.value = "break";
498     else if (body.indexOf("return") != -1)
499         breakpoint.value = "__drosera_breakpoint_conditional_func = function() {" + body + "}; __drosera_breakpoint_conditional_func();";
500     else
501         breakpoint.value = "__drosera_breakpoint_conditional_func = function() { return((" + body + ")); }; __drosera_breakpoint_conditional_func();";
502 }
503
504 function toggleBreakpointOnLine(lineNum)
505 {
506     var breakpoint = files[currentFile].breakpoints[lineNum];
507     pendingAction = null;
508     if (breakpoint.enabled)
509         breakpoint.row.addStyleClass("disabled");    
510     else
511         breakpoint.row.removeStyleClass("disabled");
512     
513     var hack = breakpoint.row.offsetTop; // force a relayout if needed.
514     
515     breakpoint.enabled = !breakpoint.enabled;
516     var editor = breakpoint.editor;
517     if (editor) {
518         editor.query('.//input[@class="enable"]').checked = breakpoint.enabled;
519         setConditionFieldText(editor, lineNum);
520     }
521 }
522
523 function moveBreakPoint(event)
524 {
525     if (event.target.parentNode.hasStyleClass("breakpoint")) {
526         draggingBreakpoint = event.target;
527         draggingBreakpoint.started = false;
528         draggingBreakpoint.dragLastY = event.clientY + window.scrollY;
529         draggingBreakpoint.dragLastX = event.clientX + window.scrollX;
530         var sourcesDocument = document.getElementById("sources").contentDocument;
531         sourcesDocument.addEventListener("mousemove", breakpointDrag, true);
532         sourcesDocument.addEventListener("mouseup", breakpointDragEnd, true);
533         sourcesDocument.body.style.cursor = "default";
534     }
535 }
536
537 function breakpointDrag(event)
538 {
539     var sourcesDocument = document.getElementById("sources").contentDocument;
540     if (!draggingBreakpoint) {
541         sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
542         sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
543         sourcesDocument.body.style.removeProperty("cursor");
544         return;
545     }
546
547     var x = event.clientX + window.scrollX;
548     var y = event.clientY + window.scrollY;
549     var deltaX = draggingBreakpoint.dragLastX - x;
550     var deltaY = draggingBreakpoint.dragLastY - y;
551     if (draggingBreakpoint.started || deltaX > 4 || deltaY > 4 || deltaX < -4 || deltaY < -4) {
552     
553         if (!draggingBreakpoint.started) {
554             var lineNum = draggingBreakpoint.title;
555             var file = files[currentFile];
556             var breakpoint = file.breakpoints[lineNum];
557             draggingBreakpoint.breakpoint = breakpoint;
558             breakpoint.row.removeStyleClass("breakpoint");
559             breakpoint.row.removeStyleClass("disabled");
560
561             var editor = breakpoint.editor;
562             if (editor)
563                 toggleBreakpointEditorOnLine(lineNum);
564             
565             draggingBreakpoint.started = true;
566                         
567             file.breakpoints[lineNum] = null;
568
569             var dragImage = sourcesDocument.createElement("img");
570             if (draggingBreakpoint.breakpoint.enabled)
571                 dragImage.src = "breakPoint.tif";
572             else
573                 dragImage.src = "breakPointDisabled.tif";
574
575             dragImage.id = "breakpointDrag";
576             dragImage.style.top = y - 8 + "px";
577             dragImage.style.left = x - 12 + "px";
578             sourcesDocument.body.appendChild(dragImage);
579         } else {
580             var dragImage = sourcesDocument.getElementById("breakpointDrag");
581             if (!dragImage) {
582                 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
583                 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
584                 sourcesDocument.body.style.removeProperty("cursor");
585                 return;
586             }
587
588             dragImage.style.top = y - 8 + "px";
589             dragImage.style.left = x - 12 + "px";
590             if (x > 40)
591                 dragImage.style.visibility = "hidden";
592             else
593                 dragImage.style.removeProperty("visibility");
594         }
595
596         draggingBreakpoint.dragLastX = x;
597         draggingBreakpoint.dragLastY = y;
598     }
599 }
600
601 function breakpointDragEnd(event)
602 {
603     var sourcesDocument = document.getElementById("sources").contentDocument;
604     sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
605     sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
606     sourcesDocument.body.style.removeProperty("cursor");
607
608     var dragImage = sourcesDocument.getElementById("breakpointDrag");
609     if (!dragImage)
610         return;
611         
612     dragImage.parentNode.removeChild(dragImage);
613
614     var x = event.clientX + window.scrollX;
615     if (x > 40 || !draggingBreakpoint)
616         return;
617
618     var y = event.clientY + window.scrollY;
619     var rowHeight = draggingBreakpoint.parentNode.offsetHeight;
620     var row = Math.ceil(y / rowHeight);
621     if (row <= 0)
622         row = 1;
623
624     var file = files[currentFile];
625     var table = file.element.firstChild;
626     if (row > table.childNodes.length)
627         return;
628
629     var tr = table.childNodes.item(row - 1);
630     if (!tr)
631         return;
632         
633     var breakpoint = draggingBreakpoint.breakpoint;
634     breakpoint.row = tr;
635     
636     // leave the editor there if it exists... we'll want to update it to the new values
637     breakpoint.editor = file.breakpoints[row].editor;
638     
639     file.breakpoints[row] = breakpoint;
640     
641     if (breakpoint.editor) {
642         breakpoint.editor.id = row;
643         updateBreakpointTypeOnLine(row);
644         setConditionFieldText(breakpoint);
645     }
646     
647     if (!breakpoint.enabled)
648         tr.addStyleClass("disabled");
649
650     tr.addStyleClass("breakpoint");
651     
652     draggingBreakpoint = null;
653 }
654
655 function totalOffsetTop(element, stop)
656 {
657     var currentTop = 0;
658     while (element.offsetParent) {
659         currentTop += element.offsetTop
660         element = element.offsetParent;
661         if (element == stop)
662             break;
663     }
664     return currentTop;
665 }
666
667 function switchFile()
668 {
669     var filesSelect = document.getElementById("files");
670     fileClicked(filesSelect.options[filesSelect.selectedIndex].value, false);
671     loadFile(filesSelect.options[filesSelect.selectedIndex].value, true);
672 }
673
674 function syntaxHighlight(code, file)
675 {
676     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 };
677
678     function echoChar(c) {
679         if (c == '<')
680             result += '&lt;';
681         else if (c == '>')
682             result += '&gt;';
683         else if (c == '&')
684             result += '&amp;';
685         else if (c == '\t')
686             result += '    ';
687         else
688             result += c;
689     }
690
691     function isDigit(number) {
692         var string = "1234567890";
693         if (string.indexOf(number) != -1)
694             return true;
695         return false;
696     }
697
698     function isHex(hex) {
699         var string = "1234567890abcdefABCDEF";
700         if (string.indexOf(hex) != -1)
701             return true;
702         return false;
703     }
704
705     function isLetter(letter) {
706         var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
707         if (string.indexOf(letter) != -1)
708             return true;
709         return false;
710     }
711
712     var result = "";
713     var cPrev = "";
714     var c = "";
715     var cNext = "";
716     for (var i = 0; i < code.length; i++) {
717         cPrev = c;
718         c = code.charAt(i);
719         cNext = code.charAt(i + 1);
720
721         if (c == "/" && cNext == "*") {
722             result += "<span class=\"comment\">";
723             echoChar(c);
724             echoChar(cNext);
725             for (i += 2; i < code.length; i++) {
726                 c = code.charAt(i);
727                 if (c == "\n")
728                     result += "</span>";
729                 echoChar(c);
730                 if (c == "\n")
731                     result += "<span class=\"comment\">";
732                 if (cPrev == "*" && c == "/")
733                     break;
734                 cPrev = c;
735             }
736             result += "</span>";
737             continue;
738         } else if (c == "/" && cNext == "/") {
739             result += "<span class=\"comment\">";
740             echoChar(c);
741             echoChar(cNext);
742             for (i += 2; i < code.length; i++) {
743                 c = code.charAt(i);
744                 if (c == "\n")
745                     break;
746                 echoChar(c);
747             }
748             result += "</span>";
749             echoChar(c);
750             continue;
751         } else if (c == "\"" || c == "'") {
752             var instringtype = c;
753             var stringstart = i;
754             result += "<span class=\"string\">";
755             echoChar(c);
756             for (i += 1; i < code.length; i++) {
757                 c = code.charAt(i);
758                 if (stringstart < (i - 1) && cPrev == instringtype && code.charAt(i - 2) != "\\")
759                     break;
760                 echoChar(c);
761                 cPrev = c;
762             }
763             result += "</span>";
764             echoChar(c);
765             continue;
766         } else if (c == "0" && cNext == "x" && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
767             result += "<span class=\"number\">";
768             echoChar(c);
769             echoChar(cNext);
770             for (i += 2; i < code.length; i++) {
771                 c = code.charAt(i);
772                 if (!isHex(c))
773                     break;
774                 echoChar(c);
775             }
776             result += "</span>";
777             echoChar(c);
778             continue;
779         } else if ((isDigit(c) || ((c == "-" || c == ".") && isDigit(cNext))) && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
780             result += "<span class=\"number\">";
781             echoChar(c);
782             for (i += 1; i < code.length; i++) {
783                 c = code.charAt(i);
784                 if (!isDigit(c) && c != ".")
785                     break;
786                 echoChar(c);
787             }
788             result += "</span>";
789             echoChar(c);
790             continue;
791         } else if (isLetter(c) && (i == 0 || !isLetter(cPrev))) {
792             var keyword = c;
793             var cj = "";
794             for (var j = i + 1; j < i + 12 && j < code.length; j++) {
795                 cj = code.charAt(j);
796                 if (!isLetter(cj))
797                     break;
798                 keyword += cj;
799             }
800
801             if (keywords[keyword]) {
802                 var functionName = "";
803                 var functionIsAnonymous = false;
804                 if (keyword == "function") {
805                     var functionKeywordOffset = 8;
806                     for (var j = i + functionKeywordOffset; j < code.length; j++) {
807                         cj = code.charAt(j);
808                         if (cj == " ")
809                             continue;
810                         if (cj == "(")
811                             break;
812                         functionName += cj;
813                     }
814
815                     if (!functionName.length) {
816                         functionIsAnonymous = true;
817                         var functionAssignmentFound = false;
818                         var functionNameStart = -1;
819                         var functionNameEnd = -1;
820
821                         for (var j = i - 1; j >= 0; j--) {
822                             cj = code.charAt(j);
823                             if (cj == ":" || cj == "=") {
824                                 functionAssignmentFound = true;
825                                 continue;
826                             }
827
828                             var curCharIsSpace = (cj == " " || cj == "\t" || cj == "\n");
829                             if (functionAssignmentFound && functionNameEnd == -1 && !curCharIsSpace) {
830                                 functionNameEnd = j + 1;
831                             } else if (!functionAssignmentFound && !curCharIsSpace) {
832                                 break;
833                             } else if (functionNameEnd != -1 && curCharIsSpace) {
834                                 functionNameStart = j;
835                                 break;
836                             }
837                         }
838
839                         if (functionNameStart != -1 && functionNameEnd != -1)
840                             functionName = code.substring(functionNameStart, functionNameEnd);
841                     }
842
843                     if (!functionName.length)
844                         functionName = "function";
845
846                     file.functionNames.push(functionName);
847                 }
848
849                 var fileIndex = filesLookup[file.url];
850
851                 if (keyword == "function") 
852                     result += "<span class=\"keyword\"><a name=\"function-" + fileIndex + "-" + file.functionNames.length + "\" id=\"" + fileIndex + "-" + file.functionNames.length + "\">" + keyword + "</a></span>";
853                 else
854                     result += "<span class=\"keyword\">" + keyword + "</span>";
855
856                 if (functionName.length && !functionIsAnonymous) {
857                     result += " <a name=\"function-" + fileIndex + "-" + file.functionNames.length + "\" id=\"" + fileIndex + "-" + file.functionNames.length + "\">" + functionName + "</a>";
858                     i += keyword.length + functionName.length;
859                 } else
860                     i += keyword.length - 1;
861
862                 continue;
863             }
864         }
865
866         echoChar(c);
867     }
868         
869     return result;
870 }
871
872 function navFilePrevious(element)
873 {
874     if (element.disabled)
875         return;
876     var lastFile = previousFiles.pop();
877     if (currentFile != -1)
878         nextFiles.unshift(currentFile);
879     loadFile(lastFile, false);
880 }
881
882 function navFileNext(element)
883 {
884     if (element.disabled)
885         return;
886     var lastFile = nextFiles.shift();
887     if (currentFile != -1)
888         previousFiles.push(currentFile);
889     loadFile(lastFile, false);
890 }
891
892 function updateFunctionStack()
893 {
894     var stackframeTable = document.getElementById("stackframeTable");
895     stackframeTable.innerHTML = ""; // clear the content
896
897     currentStack = new Array();
898     var stack = DebuggerDocument.currentFunctionStack();
899     for(var i = 0; i < stack.length; i++) {
900         var tr = document.createElement("tr");
901         var td = document.createElement("td");
902         td.className = "stackNumber";
903         td.innerText = i;
904         tr.appendChild(td);
905
906         td = document.createElement("td");
907         td.innerText = stack[i];
908         tr.appendChild(td);
909         tr.addEventListener("click", selectStackFrame, true);
910
911         stackframeTable.appendChild(tr);
912
913         var frame = new ScriptCallFrame(stack[i], i, tr);
914         tr.callFrame = frame;
915         currentStack.push(frame);
916
917         if (i == 0) {
918             tr.addStyleClass("current");
919             frame.loadVariables();
920             currentCallFrame = frame;
921         }
922     }
923 }
924
925 function selectStackFrame(event)
926 {
927     var stackframeTable = document.getElementById("stackframeTable");
928     var rows = stackframeTable.childNodes;
929     for (var i = 0; i < rows.length; i++)
930         rows[i].removeStyleClass("current");
931     this.addStyleClass("current");
932     this.callFrame.loadVariables();
933     currentCallFrame = this.callFrame;
934
935     if (frameLineNumberInfo = frameLineNumberStack[this.callFrame.index - 1])
936         jumpToLine(frameLineNumberInfo[0], frameLineNumberInfo[1]);
937     else if (this.callFrame.index == 0)
938         jumpToLine(lastStatement[0], lastStatement[1]);
939 }
940
941 function selectVariable(event)
942 {
943     var variablesTable = document.getElementById("variablesTable");
944     var rows = variablesTable.childNodes;
945     for (var i = 0; i < rows.length; i++)
946         rows[i].removeStyleClass("current");
947     this.addStyleClass("current");
948 }
949
950 function switchFunction(index, shouldResetPopup)
951 {
952     if (shouldResetPopup === undefined) shouldResetPopup = false;
953     var sourcesFrame = window.frames['sourcesFrame'];
954     
955     if (shouldResetPopup || index == 0) {
956         document.getElementById("functionPopupButtonContent").innerHTML = '<span class="placeholder">&lt;No selected symbol&gt;</span>';
957         return;
958     }
959
960     var functionSelect = document.getElementById("functions");
961     var selectedFunction = functionSelect.childNodes[index];
962     var selection = sourcesFrame.getSelection();
963     var currentFunction = selectedFunction.value;     
964     var currentFunctionElement = sourcesFrame.document.getElementById(currentFunction);
965     
966     functionSelect.blur();
967     sourcesFrame.focus();
968     selection.setBaseAndExtent(currentFunctionElement, 0, currentFunctionElement, 1);
969     sourcesFrame.location.hash = "#function-" + selectedFunction.value;
970     document.getElementById("functionPopupButtonContent").innerText = selectedFunction.innerText;
971 }
972
973 function loadFile(fileIndex, manageNavLists)
974 {
975     var file = files[fileIndex];
976     if (!file)
977         return;
978
979     if (currentFile != -1 && files[currentFile] && files[currentFile].element)
980         files[currentFile].element.style.display = "none";
981
982     if (!file.loaded) {
983         var sourcesDocument = document.getElementById("sources").contentDocument;
984         var sourcesDiv = sourcesDocument.body;
985         var sourceDiv = sourcesDocument.createElement("div");
986         sourceDiv.id = "file" + fileIndex;
987         sourcesDiv.appendChild(sourceDiv);
988         file.element = sourceDiv;
989
990         var table = sourcesDocument.createElement("table");
991         sourceDiv.appendChild(table);
992
993         var normalizedSource = file.source.replace(/\r\n|\r/, "\n"); // normalize line endings
994         var lines = syntaxHighlight(normalizedSource, file).split("\n");
995         for( var i = 0; i < lines.length; i++ ) {
996             var tr = sourcesDocument.createElement("tr");
997             var td = sourcesDocument.createElement("td");
998             td.className = "gutter";
999             td.title = (i + 1);
1000             td.addEventListener("click", breakpointAction, true);
1001             td.addEventListener("dblclick", function() { toggleBreakpointEditorOnLine(event.target.title); }, true);
1002             td.addEventListener("mousedown", moveBreakPoint, true);
1003             tr.appendChild(td);
1004
1005             td = sourcesDocument.createElement("td");
1006             td.className = "source";
1007             td.innerHTML = (lines[i].length ? lines[i] : "&nbsp;");
1008             tr.appendChild(td);
1009             table.appendChild(tr);
1010         }
1011         
1012         file.loaded = true;
1013     }
1014
1015     file.element.style.removeProperty("display");
1016
1017     document.getElementById("filesPopupButtonContent").innerText = (file.url ? file.url : "(unknown script)");
1018     
1019     var filesSelect = document.getElementById("files");
1020     for (var i = 0; i < filesSelect.childNodes.length; i++) {
1021         if (filesSelect.childNodes[i].value == fileIndex) {
1022             filesSelect.selectedIndex = i;
1023             break;
1024         }
1025     }
1026
1027     // Populate the function names pop-up
1028     if (file.functionNames.length > 0) {
1029         var functionSelect = document.getElementById("functions");
1030         var functionOption = document.createElement("option");
1031         
1032         document.getElementById("functionNamesPopup").style.display = "inline";
1033         switchFunction(0, true);
1034         
1035         functionSelect.removeChildren();
1036         functionOption.value = null;
1037         functionOption.text = "< no selected symbol >";
1038         functionSelect.appendChild(functionOption);
1039         
1040         for (var i = 0; i < file.functionNames.length; i++) {
1041             functionOption = document.createElement("option");
1042             functionOption.value = fileIndex + "-" + (i+1);
1043             functionOption.text = file.functionNames[i] + "()";
1044             functionSelect.appendChild(functionOption);
1045         }
1046     } else
1047         document.getElementById("functionNamesPopup").style.display = "none";
1048     
1049     if (manageNavLists) {
1050         nextFiles = new Array();
1051         if (currentFile != -1)
1052             previousFiles.push(currentFile);
1053     }
1054
1055     document.getElementById("navFileLeftButton").disabled = (previousFiles.length == 0);
1056     document.getElementById("navFileRightButton").disabled = (nextFiles.length == 0);
1057
1058     currentFile = fileIndex;
1059 }
1060
1061 function updateFileSource(source, url, force)
1062 {
1063     var fileIndex = filesLookup[url];
1064     if (!fileIndex || !source.length)
1065         return;
1066
1067     var file = files[fileIndex];
1068     if (force || file.source.length != source.length || file.source != source) {
1069         file.source = source;
1070         file.loaded = false;
1071
1072         if (file.element) {
1073             file.element.parentNode.removeChild(file.element);
1074             file.element = null;
1075         }
1076
1077         if (currentFile == fileIndex)
1078             loadFile(fileIndex, false);
1079     }
1080 }
1081
1082 /**
1083 * ParsedURL - this object chops up full URL into two parts: 
1084  * 1) The domain: everything from http:// to the end of the domain name
1085  * 2) The relative path: everything after the domain
1086  *
1087  * @param string url URL to be processed
1088  */
1089 function ParsedURL(url)
1090 {
1091     // Since we're getting the URL from the browser, we're safe to assume the URL is already well formatted
1092     // and so there is no need for more sophisticated regular expression here
1093     var url_parts = ((url.substring(0,4)).toLowerCase() == "file") ? url.match(/(file:[\/]{2,3}(\w|\.|-|_|\/)+)\/(.*)/) : url.match(/(http[s]?:\/\/(www)?\.?(\w|\.|-)+\w(:\d{1,5})?)\/?(.*)/);    
1094     // the domain here is considered the whole http://www.example.org:8000 or file:///Users/user/folder/file.htm string for display purposes
1095     this.domain = url_parts[1];
1096     // the relative path is everything following the domain
1097     this.relativePath = (url_parts[5] === undefined) ? "/" + url_parts[3] : "/" + url_parts[5];
1098 }
1099
1100 /**
1101 * SiteBrowser - modifies the file tree via DOM as new files are being open
1102  *
1103  */
1104 function SiteBrowser()
1105 {
1106     var fileBrowser = document.getElementById("filesBrowserSites");
1107     
1108     this.addURL = function add(url, fileIndex)
1109     {
1110         var parsedURL = new ParsedURL(url);
1111         var divs = fileBrowser.getElementsByTagName("div");
1112         
1113         if (divs.length == 0) { 
1114             addNewDomain(parsedURL, fileIndex);
1115         } else {
1116             var isNew = true;
1117             for (var i = 0; i < divs.length; i++) {
1118                 if (divs[i].id == parsedURL.domain) {
1119                     var uls = divs[i].getElementsByTagName("ul");
1120                     var ul = (uls.length > 0) ? uls[0] : document.createElement("ul");
1121                     var li = document.createElement("li");
1122                     
1123                     li.id = fileIndex;
1124                     li.addEventListener("click", fileBrowserMouseEvents, false);
1125                     li.title = li.innerText = parsedURL.relativePath ? parsedURL.relativePath : "/";
1126                     ul.appendChild(li);
1127                     isNew = false;
1128                     break;
1129                 }
1130             }
1131             if (isNew) {
1132                 addNewDomain(parsedURL, fileIndex);
1133             }
1134         }
1135     }
1136     
1137     this.selectInitialFile = function sf()
1138     {
1139         if (currentFile == -1)
1140             document.getElementById("1").className = "active";
1141     }
1142     
1143     function addNewDomain(parsedURL, fileIndex)
1144     {
1145         var div = document.createElement("div");
1146         var ul = document.createElement("ul");
1147         var li = document.createElement("li");
1148         
1149         div.id = div.innerText = div.title = parsedURL.domain;
1150         div.addEventListener("click", fileBrowserMouseEvents, false);
1151         // Maybe we can add some roll-overs here...
1152         //div.addEventListener("mouseover", fileBrowserMouseEvents, false);
1153         //div.addEventListener("mouseout", fileBrowserMouseEvents, false);
1154         li.id = fileIndex;
1155         li.addEventListener("click", fileBrowserMouseEvents, false);
1156         li.title = li.innerText = parsedURL.relativePath ? parsedURL.relativePath : "/";
1157         ul.appendChild(li);
1158         div.appendChild(ul);
1159         fileBrowser.appendChild(div);        
1160     }
1161 }
1162
1163 function fileBrowserMouseEvents(event)
1164 {
1165     switch (event.type)
1166     {
1167         case "click":
1168             // If we clicked on a site, collapse/expand it, if on a file, display it. Since we're only capturing this
1169             // event from either a DIV or LI element, we don't have to worry about any ambiguity
1170             (event.target.nodeName.toUpperCase() == "DIV") ? toggleCollapseSite(event) : fileClicked(event.target.id);
1171             break;
1172     }
1173 }
1174
1175 function fileClicked(fileId, shouldLoadFile)
1176 {
1177     if (shouldLoadFile === undefined) shouldLoadFile = true;
1178     
1179     document.getElementById(currentFile).className = "passive";
1180     document.getElementById(fileId).className = "active";
1181     if (shouldLoadFile) 
1182         loadFile(fileId, false);
1183 }
1184
1185 function toggleCollapseSite(event)
1186 {
1187     var thisSite = document.getElementById(event.target.id);
1188     var siteFiles = thisSite.getElementsByTagName("ul");
1189     
1190     if (siteFiles[0].style.display == "block" || !siteFiles[0].style.display) {
1191         siteFiles[0].style.display = "none";
1192         thisSite.className = "collapsed"; 
1193     } else {
1194         siteFiles[0].style.display = "block";
1195         thisSite.className = "expanded";
1196     }
1197 }
1198
1199 function didParseScript(source, fileSource, url, sourceId, baseLineNumber)
1200 {
1201     var fileIndex = filesLookup[url];
1202     var file = files[fileIndex];
1203     var firstLoad = false;
1204
1205     if (!fileIndex || !file) {
1206         fileIndex = files.length + 1;
1207         if (url.length)
1208             filesLookup[url] = fileIndex;
1209
1210         file = new Object();
1211         file.scripts = new Array();
1212         file.breakpoints = new Array();
1213         file.functionNames = new Array();
1214         file.source = (fileSource.length ? fileSource : source);
1215         file.url = (url.length ? url : null);
1216         file.loaded = false;
1217
1218         files[fileIndex] = file;
1219
1220         var filesSelect = document.getElementById("files");
1221         var option = document.createElement("option");
1222         files[fileIndex].menuOption = option;
1223         option.value = fileIndex;
1224         option.text = (file.url ? file.url : "(unknown script)");
1225         filesSelect.appendChild(option);
1226
1227         var siteBrowser = new SiteBrowser();
1228         siteBrowser.addURL(file.url, fileIndex);
1229         siteBrowser.selectInitialFile();        
1230         
1231         firstLoad = true;
1232     }
1233
1234     var sourceObj = new Object();
1235     sourceObj.file = fileIndex;
1236     sourceObj.baseLineNumber = baseLineNumber;
1237     file.scripts.push(sourceId);
1238     scripts[sourceId] = sourceObj;
1239
1240     if (!firstLoad)
1241         updateFileSource((fileSource.length ? fileSource : source), url, false);
1242
1243     if (currentFile == -1)
1244         loadFile(fileIndex, false);
1245 }
1246
1247 function jumpToLine(sourceId, line)
1248 {
1249     var script = scripts[sourceId];
1250     if (line <= 0 || !script)
1251         return;
1252
1253     var file = files[script.file];
1254     if (!file)
1255         return;
1256
1257     if (currentFile != script.file)
1258         loadFile(script.file, true);
1259     if (currentRow)
1260         currentRow.removeStyleClass("current");
1261     if (!file.element)
1262         return;
1263     if (line > file.element.firstChild.childNodes.length)
1264         return;
1265
1266     currentRow = file.element.firstChild.childNodes.item(line - 1);
1267     if (!currentRow)
1268         return;
1269
1270     currentRow.addStyleClass("current");
1271
1272     var sourcesDiv = document.getElementById("sources");
1273     var sourcesDocument = document.getElementById("sources").contentDocument;
1274     var parent = sourcesDocument.body;
1275     var offset = totalOffsetTop(currentRow, parent);
1276     if (offset < (parent.scrollTop + 20) || offset > (parent.scrollTop + sourcesDiv.clientHeight - 20))
1277         parent.scrollTop = totalOffsetTop(currentRow, parent) - (sourcesDiv.clientHeight / 2) + 10;
1278 }
1279
1280 function willExecuteStatement(sourceId, line, fromLeavingFrame)
1281 {
1282     var script = scripts[sourceId];
1283     if (line <= 0 || !script)
1284         return;
1285
1286     var file = files[script.file];
1287     if (!file)
1288         return;
1289
1290     lastStatement = [sourceId, line];
1291     
1292     var breakpoint = file.breakpoints[line];
1293
1294     var shouldBreak = false;
1295
1296     if (breakpoint && breakpoint.enabled) {
1297         switch(breakpoint.type) {
1298             case 0:
1299                 shouldBreak = (breakpoint.value == "break" || DebuggerDocument.evaluateScript_inCallFrame_(breakpoint.value, 0) == 1);
1300                 if (shouldBreak)
1301                     breakpoint.hitcount++;
1302                 break;
1303             case 1:
1304                 var message = "Hit breakpoint on line " + line;
1305                 if (breakpoint.value != "break")
1306                     message = DebuggerDocument.evaluateScript_inCallFrame_(breakpoint.value, 0);
1307                 if (consoleWindow)
1308                     consoleWindow.appendMessage("", message);
1309                 breakpoint.hitcount++;
1310                 break;
1311         }
1312         var editor = breakpoint.editor;
1313         var counter = null;
1314         if (editor)
1315             counter = breakpoint.editor.query('.//span[@class="hitCounter"]');
1316         if (counter)
1317             counter.innerText = breakpoint.hitcount;
1318     }
1319     
1320     if (pauseOnNextStatement || shouldBreak || (steppingOver && !steppingStack)) {
1321         pause();
1322         pauseOnNextStatement = false;
1323         pausedWhileLeavingFrame = fromLeavingFrame || false;
1324     }
1325
1326     if (isPaused()) {
1327         updateFunctionStack();
1328         jumpToLine(sourceId, line);
1329     }
1330 }
1331
1332 function didEnterCallFrame(sourceId, line)
1333 {
1334     if (steppingOver || steppingOut)
1335         steppingStack++;
1336
1337     if (lastStatement)
1338         frameLineNumberStack.unshift(lastStatement);
1339     willExecuteStatement(sourceId, line);
1340 }
1341
1342 function willLeaveCallFrame(sourceId, line)
1343 {
1344     if (line <= 0)
1345         resume();
1346     willExecuteStatement(sourceId, line, true);
1347     frameLineNumberStack.shift();
1348     if (!steppingStack)
1349         steppingOver = false;
1350     if (steppingOut && !steppingStack) {
1351         steppingOut = false;
1352         pauseOnNextStatement = true;
1353     }
1354     if ((steppingOver || steppingOut) && steppingStack >= 1)
1355         steppingStack--;
1356 }
1357
1358 function exceptionWasRaised(sourceId, line)
1359 {
1360     pause();
1361     updateFunctionStack();
1362     jumpToLine(sourceId, line);
1363 }
1364
1365 function showConsoleWindow()
1366 {
1367     if (!consoleWindow)
1368         consoleWindow = window.open("console.html", "console", "top=200, left=200, width=500, height=300, toolbar=yes, resizable=yes");
1369     else
1370         consoleWindow.focus();
1371 }