53391e07b16027892c4c0e3fc106eb710c7e711d
[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 enabledBreakpoint = "break";
51 var breakpointEditorHTML = '<div class="top">Edit Breakpoint:<label><input type="checkbox" onclick="window.parent.toggleBreakpointForEditor(this.parentNode.parentNode.parentNode)"></input>Enable</label><button class="save" onclick="window.parent.saveBreakpointForEditor(this.parentNode.parentNode)">Save</button></div><div class="bottom"><label for="editorCondition">Condition:</label><div class="condition"></div></div>'
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     var now = new Date();
92     var exitTime = now.getTime() + numberMillis;
93     while (true) {
94         now = new Date();
95         if (now.getTime() > exitTime)
96             return;
97     }
98 }
99
100 function headerMouseDown(element) {
101     if (!isResizingColumn) 
102         element.style.background = "url(glossyHeaderPressed.png) repeat-x";
103 }
104
105 function headerMouseUp(element) {
106     element.style.background = "url(glossyHeader.png) repeat-x";
107 }
108
109 function headerMouseOut(element) {
110     element.style.background = "url(glossyHeader.png) repeat-x";
111 }
112
113 function filesDividerDragStart(event) 
114 {
115     dividerDragStart(document.getElementById("filesDivider"), filesDividerDrag, filesDividerDragEnd, event, "col-resize");
116 }
117
118 function filesDividerDragEnd(event) 
119 {
120     dividerDragEnd(document.getElementById("filesDivider"), filesDividerDrag, filesDividerDragEnd, event);
121 }
122
123 function filesDividerDrag(event) 
124 {
125     var element = document.getElementById("filesDivider");
126     if (document.getElementById("filesDivider").dragging == true) {
127         var masterMain = document.getElementById("masterMain");
128         var main = document.getElementById("main");
129         var fileBrowser = document.getElementById("fileBrowser");
130         var x = event.clientX + window.scrollX;
131         var delta = element.dragLastX - x;
132         var newWidth = constrainedWidthFromElement(fileBrowser.clientWidth - delta, masterMain, 0.1, 0.9);
133         if ((fileBrowser.clientWidth - delta) == newWidth) // the width wasn't constrained
134             element.dragLastX = x;
135         fileBrowser.style.width = newWidth + "px";
136         main.style.left = newWidth + "px";
137         event.preventDefault();
138     }
139 }
140
141 function dividerDragStart(element, dividerDrag, dividerDragEnd, event, cursor) {
142     element.dragging = true;
143     element.dragLastY = event.clientY + window.scrollY;
144     element.dragLastX = event.clientX + window.scrollX;
145     document.addEventListener("mousemove", dividerDrag, true);
146     document.addEventListener("mouseup", dividerDragEnd, true);
147     document.body.style.cursor = cursor;
148     event.preventDefault();
149 }
150
151 function dividerDragEnd(element, dividerDrag, dividerDragEnd, event) {
152     element.dragging = false;
153     document.removeEventListener("mousemove", dividerDrag, true);
154     document.removeEventListener("mouseup", dividerDragEnd, true);
155     document.body.style.cursor = null;
156 }
157
158 function dividerDrag(event) {
159     var element = document.getElementById("divider");
160     if (document.getElementById("divider").dragging == true) {
161         var main = document.getElementById("main");
162         var top = document.getElementById("info");
163         var bottom = document.getElementById("body");
164         var y = event.clientY + window.scrollY;
165         var delta = element.dragLastY - y;
166         var newHeight = constrainedHeightFromElement(top.clientHeight - delta, main);
167         if ((top.clientHeight - delta) == newHeight) // the height wasn't constrained
168             element.dragLastY = y;
169         top.style.height = newHeight + "px";
170         bottom.style.top = newHeight + "px";
171         event.preventDefault();
172     }
173 }
174
175 function sourceDividerDragStart(event) {
176     dividerDragStart(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event, "row-resize");
177 }
178
179 function sourceDividerDragEnd(event) {
180     dividerDragEnd(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event);
181 }
182
183 function infoDividerDragStart(event) {
184     dividerDragStart(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event, "col-resize");
185 }
186
187 function infoDividerDragEnd(event) {
188     dividerDragEnd(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event);
189 }
190
191 function infoDividerDrag(event) {
192     var element = document.getElementById("infoDivider");
193     if (document.getElementById("infoDivider").dragging == true) {
194         var main = document.getElementById("main");
195         var leftPane = document.getElementById("leftPane");
196         var rightPane = document.getElementById("rightPane");
197         var x = event.clientX + window.scrollX;
198         var delta = element.dragLastX - x;
199         var newWidth = constrainedWidthFromElement(leftPane.clientWidth - delta, main);
200         if ((leftPane.clientWidth - delta) == newWidth) // the width wasn't constrained
201             element.dragLastX = x;
202         leftPane.style.width = newWidth + "px";
203         rightPane.style.left = newWidth + "px";
204         event.preventDefault();
205     }
206 }
207
208 function columnResizerDragStart(event) {
209     isResizingColumn = true;
210     dividerDragStart(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event, "col-resize");
211 }
212
213 function columnResizerDragEnd(event) {
214     isResizingColumn = false;
215     dividerDragEnd(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
216 }
217
218 function columnResizerDrag(event) {
219     var element = document.getElementById("variableColumnResizer");
220     if (element.dragging == true) {
221         var main = document.getElementById("rightPane");
222         var variableColumn = document.getElementById("variable");
223         var rules = document.defaultView.getMatchedCSSRules(variableColumn, "");
224         for (var i = 0; i < rules.length; i++) {
225             if (rules[i].selectorText == ".variable") {
226                 var columnRule = rules[i];
227                 break;
228             }
229         }
230
231         var x = event.clientX + window.scrollX;
232         var delta = element.dragLastX - x;
233         var newWidth = constrainedWidthFromElement(variableColumn.clientWidth - delta, main);
234         if ((variableColumn.clientWidth - delta) == newWidth) // the width wasn't constrained
235             element.dragLastX = x;
236         columnRule.style.width = newWidth + "px";
237         element.style.left = newWidth + "px";
238         event.preventDefault();
239     }
240 }
241
242 function constrainedWidthFromElement(width, element, constrainLeft, constrainRight) {
243     if (constrainLeft === undefined) constrainLeft = 0.25;
244     if (constrainRight === undefined) constrainRight = 0.75;
245     
246     if (width < element.clientWidth * constrainLeft)
247         width = element.clientWidth * constrainLeft;
248     else if (width > element.clientWidth * constrainRight)
249         width = element.clientWidth * constrainRight;
250     return width;
251 }
252
253 function constrainedHeightFromElement(height, element) {
254     if (height < element.clientHeight * 0.25)
255         height = element.clientHeight * 0.25;
256     else if (height > element.clientHeight * 0.75)
257         height = element.clientHeight * 0.75;
258     return height;
259 }
260
261 function loaded() {
262     document.getElementById("divider").addEventListener("mousedown", sourceDividerDragStart, false);
263     document.getElementById("infoDivider").addEventListener("mousedown", infoDividerDragStart, false);
264     document.getElementById("filesDivider").addEventListener("mousedown", filesDividerDragStart, false);
265     document.getElementById("variableColumnResizer").addEventListener("mousedown", columnResizerDragStart, false);
266 }
267
268 function isPaused() {
269     return DebuggerDocument.isPaused();
270 }
271
272 function pause() {
273     DebuggerDocument.pause();
274 }
275
276 function resume()
277 {
278     if (currentRow) {
279         currentRow.removeStyleClass("current");
280         currentRow = null;
281     }
282
283     var stackframeTable = document.getElementById("stackframeTable");
284     stackframeTable.innerHTML = ""; // clear the content
285     var variablesTable = document.getElementById("variablesTable");
286     variablesTable.innerHTML = ""; // clear the content
287     currentStack = null;
288     currentCallFrame = null;
289
290     pauseOnNextStatement = false;
291     pausedWhileLeavingFrame = false;
292     steppingOut = false;
293     steppingOver = false;
294     steppingStack = 0;
295
296     DebuggerDocument.resume();
297 }
298
299 function stepInto()
300 {
301     pauseOnNextStatement = false;
302     steppingOut = false;
303     steppingOver = false;
304     steppingStack = 0;
305     DebuggerDocument.stepInto();
306 }
307
308 function stepOver()
309 {
310     pauseOnNextStatement = false;
311     steppingOver = true;
312     steppingStack = 0;
313     DebuggerDocument.resume();
314 }
315
316 function stepOut()
317 {
318     pauseOnNextStatement = pausedWhileLeavingFrame;
319     steppingOver = false;
320     steppingStack = 0;
321     steppingOut = true;
322     DebuggerDocument.resume();
323 }
324
325 Element.prototype.removeStyleClass = function(className) {
326     if (this.hasStyleClass(className))
327         this.className = this.className.replace(className, "");
328 }
329
330 Element.prototype.addStyleClass = function(className) {
331     if (!this.hasStyleClass(className))
332         this.className += (this.className.length ? " " + className : className);
333 }
334
335 Element.prototype.hasStyleClass = function(className) {
336     return this.className.indexOf(className) != -1;
337 }
338
339 function breakpointAction(event)
340 {
341     var row = event.target.parentNode;
342     var file = files[currentFile];
343     var lineNum = parseInt(event.target.title);
344     
345     if (row.hasStyleClass("breakpoint"))
346         toggleBreakpoint(row, file, lineNum);    
347     else
348         createBreakpoint(row, file, lineNum);
349 }
350
351 function createBreakpoint(row, file, lineNum)
352 {
353     row.addStyleClass("breakpoint");
354     row.removeStyleClass("disabled");
355     file.breakpoints[lineNum] = enabledBreakpoint;
356     file.disabledBreakpoints[lineNum] = null;
357 }
358
359 function toggleBreakpointEditor(event)
360 {
361     var row = event.target.parentNode;
362     var file = files[currentFile];
363     var lineNum = parseInt(event.target.title);
364     if (row.hasStyleClass("breakpoint")) {
365         var editor = file.breakpointEditors[lineNum];
366         if (!editor) {
367             var sourcesDocument = document.getElementById("sources").contentDocument;
368             editor = sourcesDocument.createElement("div");
369             editor.className = "editor";
370             editor.id = lineNum;
371             editor.innerHTML = breakpointEditorHTML;
372
373             // set the enabled checkbox to the correct state
374             editor.getElementsByTagName("input")[0].checked = !row.hasStyleClass("disabled");
375             
376             // set the condition field to reflect the breakpoint
377             setConditionFieldText(editor, lineNum);
378             
379             row.childNodes[1].appendChild(editor);
380             file.breakpointEditors[lineNum] = editor; 
381         } else {
382             row.childNodes[1].removeChild(editor);
383             file.breakpointEditors[lineNum] = null;
384         }
385     }
386 }
387
388 function setConditionFieldText(editor, lineNum)
389 {
390     var conditionField = editor.childNodes[1].childNodes[1];
391     var functionBody = files[currentFile].breakpoints[lineNum];
392     if (!functionBody)
393         functionBody = "return -1;";
394     else if (functionBody == "break")
395         functionBody = "return 1;";
396     else {
397         var startIndex = functionBody.indexOf("{") + 1;
398         var endIndex = functionBody.lastIndexOf("}");
399         functionBody = functionBody.substring(startIndex, endIndex);
400     }
401     conditionField.innerText = functionBody;
402 }
403
404 function toggleBreakpointForEditor(editor)
405 {
406     var file = files[currentFile];
407     var lineNum = parseInt(editor.id);
408     row = file.element.firstChild.childNodes.item(lineNum - 1);
409     toggleBreakpoint(row, file, lineNum);
410 }
411
412 function saveBreakpointForEditor(editor)
413 {
414     var file = files[currentFile];
415     var lineNum = parseInt(editor.id);
416     row = file.element.firstChild.childNodes.item(lineNum - 1);
417     file.breakpoints[lineNum] = "__drosera_breakpoint_conditional_func = function() { " + editor.childNodes[1].childNodes[1].innerText + " }; __drosera_breakpoint_conditional_func();";
418     row.childNodes[1].removeChild(editor);
419     file.breakpointEditors[lineNum] = null;
420 }
421
422 function toggleBreakpoint(row, file, lineNum)
423 {
424     if (row.hasStyleClass("disabled"))
425         row.removeStyleClass("disabled");    
426     else
427         row.addStyleClass("disabled");
428     
429     var temp = file.breakpoints[lineNum];
430     file.breakpoints[lineNum] = file.disabledBreakpoints[lineNum];
431     file.disabledBreakpoints[lineNum] = temp;
432     var editor = file.breakpointEditors[lineNum];
433     if (editor) {
434         editor.childNodes[0].childNodes[1].childNodes[0].checked = !row.hasStyleClass("disabled");
435         setConditionFieldText(editor, lineNum);
436     }
437 }
438
439 function moveBreakPoint(event)
440 {
441     if (event.target.parentNode.hasStyleClass("breakpoint")) {
442         draggingBreakpoint = event.target;
443         draggingBreakpoint.started = false;
444         draggingBreakpoint.dragLastY = event.clientY + window.scrollY;
445         draggingBreakpoint.dragLastX = event.clientX + window.scrollX;
446         var sourcesDocument = document.getElementById("sources").contentDocument;
447         sourcesDocument.addEventListener("mousemove", breakpointDrag, true);
448         sourcesDocument.addEventListener("mouseup", breakpointDragEnd, true);
449         sourcesDocument.body.style.cursor = "default";
450     }
451 }
452
453 function breakpointDrag(event)
454 {
455     var sourcesDocument = document.getElementById("sources").contentDocument;
456     if (!draggingBreakpoint) {
457         sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
458         sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
459         sourcesDocument.body.style.cursor = null;
460         return;
461     }
462
463     var x = event.clientX + window.scrollX;
464     var y = event.clientY + window.scrollY;
465     var deltaX = draggingBreakpoint.dragLastX - x;
466     var deltaY = draggingBreakpoint.dragLastY - y;
467     if (draggingBreakpoint.started || deltaX > 4 || deltaY > 4 || deltaX < -4 || deltaY < -4) {
468     
469         if (!draggingBreakpoint.started) {
470             var node = draggingBreakpoint.parentNode;
471             draggingBreakpoint.isDisabled = node.hasStyleClass("disabled");
472             node.removeStyleClass("breakpoint");
473             node.removeStyleClass("disabled");
474             
475             var lineNum = parseInt(draggingBreakpoint.title);
476             var file = files[currentFile];
477             
478             var editor = file.breakpointEditors[lineNum];
479             if (editor) {
480                 node.childNodes[1].removeChild(editor);
481                 file.breakpointEditors[lineNum] = null;
482             }
483             
484             draggingBreakpoint.started = true;
485                         
486             if (draggingBreakpoint.isDisabled)
487                 draggingBreakpoint.breakFunction = files[currentFile].disabledBreakpoints[lineNum];
488             else
489                 draggingBreakpoint.breakFunction = files[currentFile].breakpoints[lineNum];
490             file.breakpoints[lineNum] = null;
491             file.disabledBreakpoints[lineNum] = null;
492
493             var dragImage = sourcesDocument.createElement("img");
494             if (draggingBreakpoint.isDisabled)
495                 dragImage.src = "breakPointDisabled.tif";
496             else
497                 dragImage.src = "breakPoint.tif";
498             dragImage.id = "breakpointDrag";
499             dragImage.style.top = y - 8 + "px";
500             dragImage.style.left = x - 12 + "px";
501             sourcesDocument.body.appendChild(dragImage);
502         } else {
503             var dragImage = sourcesDocument.getElementById("breakpointDrag");
504             if (!dragImage) {
505                 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
506                 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
507                 sourcesDocument.body.style.cursor = null;
508                 return;
509             }
510
511             dragImage.style.top = y - 8 + "px";
512             dragImage.style.left = x - 12 + "px";
513             if (x > 40)
514                 dragImage.style.visibility = "hidden";
515             else
516                 dragImage.style.visibility = null;
517         }
518
519         draggingBreakpoint.dragLastX = x;
520         draggingBreakpoint.dragLastY = y;
521     }
522 }
523
524 function breakpointDragEnd(event)
525 {
526     var sourcesDocument = document.getElementById("sources").contentDocument;
527     sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
528     sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
529     sourcesDocument.body.style.cursor = null;
530
531     var dragImage = sourcesDocument.getElementById("breakpointDrag");
532     if (!dragImage)
533         return;
534         
535     dragImage.parentNode.removeChild(dragImage);
536
537     var x = event.clientX + window.scrollX;
538     if (x > 40 || !draggingBreakpoint)
539         return;
540
541     var y = event.clientY + window.scrollY;
542     var rowHeight = draggingBreakpoint.parentNode.offsetHeight;
543     var row = Math.ceil(y / rowHeight);
544     if (row <= 0)
545         row = 1;
546
547     var file = files[currentFile];
548     var table = file.element.firstChild;
549     if (row > table.childNodes.length)
550         return;
551
552     var tr = table.childNodes.item(row - 1);
553     if (!tr)
554         return;
555         
556     if (draggingBreakpoint.isDisabled) {
557         tr.addStyleClass("disabled");
558         file.disabledBreakpoints[row] = draggingBreakpoint.breakFunction;
559         file.breakpoints[row] = null;
560     } else {
561         file.disabledBreakpoints[row] = null;
562         file.breakpoints[row] = draggingBreakpoint.breakFunction;
563     }
564
565     tr.addStyleClass("breakpoint");
566     
567     draggingBreakpoint = null;
568 }
569
570 function totalOffsetTop(element, stop)
571 {
572     var currentTop = 0;
573     while (element.offsetParent) {
574         currentTop += element.offsetTop
575         element = element.offsetParent;
576         if (element == stop)
577             break;
578     }
579     return currentTop;
580 }
581
582 function switchFile()
583 {
584     var filesSelect = document.getElementById("files");
585     fileClicked(filesSelect.options[filesSelect.selectedIndex].value, false);
586     loadFile(filesSelect.options[filesSelect.selectedIndex].value, true);
587 }
588
589 function syntaxHighlight(code)
590 {
591     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 };
592
593     function echoChar(c) {
594         if (c == '<')
595             result += '&lt;';
596         else if (c == '>')
597             result += '&gt;';
598         else if (c == '&')
599             result += '&amp;';
600         else if (c == '\t')
601             result += '    ';
602         else
603             result += c;
604     }
605
606     function isDigit(number) {
607         var string = "1234567890";
608         if (string.indexOf(number) != -1)
609             return true;
610         return false;
611     }
612
613     function isHex(hex) {
614         var string = "1234567890abcdefABCDEF";
615         if (string.indexOf(hex) != -1)
616             return true;
617         return false;
618     }
619
620     function isLetter(letter) {
621         var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
622         if (string.indexOf(letter) != -1)
623             return true;
624         return false;
625     }
626
627     var result = "";
628     var cPrev = "";
629     var c = "";
630     var cNext = "";
631     for (var i = 0; i < code.length; i++) {
632         cPrev = c;
633         c = code.charAt(i);
634         cNext = code.charAt(i + 1);
635
636         if (c == "/" && cNext == "*") {
637             result += "<span class=\"comment\">";
638             echoChar(c);
639             echoChar(cNext);
640             for (i += 2; i < code.length; i++) {
641                 c = code.charAt(i);
642                 if (c == "\n")
643                     result += "</span>";
644                 echoChar(c);
645                 if (c == "\n")
646                     result += "<span class=\"comment\">";
647                 if (cPrev == "*" && c == "/")
648                     break;
649                 cPrev = c;
650             }
651             result += "</span>";
652             continue;
653         } else if (c == "/" && cNext == "/") {
654             result += "<span class=\"comment\">";
655             echoChar(c);
656             echoChar(cNext);
657             for (i += 2; i < code.length; i++) {
658                 c = code.charAt(i);
659                 if (c == "\n")
660                     break;
661                 echoChar(c);
662             }
663             result += "</span>";
664             echoChar(c);
665             continue;
666         } else if (c == "\"" || c == "'") {
667             var instringtype = c;
668             var stringstart = i;
669             result += "<span class=\"string\">";
670             echoChar(c);
671             for (i += 1; i < code.length; i++) {
672                 c = code.charAt(i);
673                 if (stringstart < (i - 1) && cPrev == instringtype && code.charAt(i - 2) != "\\")
674                     break;
675                 echoChar(c);
676                 cPrev = c;
677             }
678             result += "</span>";
679             echoChar(c);
680             continue;
681         } else if (c == "0" && cNext == "x" && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
682             result += "<span class=\"number\">";
683             echoChar(c);
684             echoChar(cNext);
685             for (i += 2; i < code.length; i++) {
686                 c = code.charAt(i);
687                 if (!isHex(c))
688                     break;
689                 echoChar(c);
690             }
691             result += "</span>";
692             echoChar(c);
693             continue;
694         } else if ((isDigit(c) || ((c == "-" || c == ".") && isDigit(cNext))) && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
695             result += "<span class=\"number\">";
696             echoChar(c);
697             for (i += 1; i < code.length; i++) {
698                 c = code.charAt(i);
699                 if (!isDigit(c) && c != ".")
700                     break;
701                 echoChar(c);
702             }
703             result += "</span>";
704             echoChar(c);
705             continue;
706         } else if (isLetter(c) && (i == 0 || !isLetter(cPrev))) {
707             var keyword = c;
708             var cj = "";
709             for (var j = i + 1; j < i + 12 && j < code.length; j++) {
710                 cj = code.charAt(j);
711                 if (!isLetter(cj))
712                     break;
713                 keyword += cj;
714             }
715
716             if (keywords[keyword]) {
717                 result += "<span class=\"keyword\">" + keyword + "</span>";
718                 i += keyword.length - 1;
719                 continue;
720             }
721         }
722
723         echoChar(c);
724     }
725
726     return result;
727 }
728
729 function navFilePrevious(element)
730 {
731     if (element.disabled)
732         return;
733     var lastFile = previousFiles.pop();
734     if (currentFile != -1)
735         nextFiles.unshift(currentFile);
736     loadFile(lastFile, false);
737 }
738
739 function navFileNext(element)
740 {
741     if (element.disabled)
742         return;
743     var lastFile = nextFiles.shift();
744     if (currentFile != -1)
745         previousFiles.push(currentFile);
746     loadFile(lastFile, false);
747 }
748
749 function updateFunctionStack()
750 {
751     var stackframeTable = document.getElementById("stackframeTable");
752     stackframeTable.innerHTML = ""; // clear the content
753
754     currentStack = new Array();
755     var stack = DebuggerDocument.currentFunctionStack();
756     for(var i = 0; i < stack.length; i++) {
757         var tr = document.createElement("tr");
758         var td = document.createElement("td");
759         td.className = "stackNumber";
760         td.innerText = i;
761         tr.appendChild(td);
762
763         td = document.createElement("td");
764         td.innerText = stack[i];
765         tr.appendChild(td);
766         tr.addEventListener("click", selectStackFrame, true);
767
768         stackframeTable.appendChild(tr);
769
770         var frame = new ScriptCallFrame(stack[i], i, tr);
771         tr.callFrame = frame;
772         currentStack.push(frame);
773
774         if (i == 0) {
775             tr.addStyleClass("current");
776             frame.loadVariables();
777             currentCallFrame = frame;
778         }
779     }
780 }
781
782 function selectStackFrame(event)
783 {
784     var stackframeTable = document.getElementById("stackframeTable");
785     var rows = stackframeTable.childNodes;
786     for (var i = 0; i < rows.length; i++)
787         rows[i].removeStyleClass("current");
788     this.addStyleClass("current");
789     this.callFrame.loadVariables();
790     currentCallFrame = this.callFrame;
791
792     if (frameLineNumberInfo = frameLineNumberStack[this.callFrame.index - 1])
793         jumpToLine(frameLineNumberInfo[0], frameLineNumberInfo[1]);
794     else if (this.callFrame.index == 0)
795         jumpToLine(lastStatement[0], lastStatement[1]);
796 }
797
798 function selectVariable(event)
799 {
800     var variablesTable = document.getElementById("variablesTable");
801     var rows = variablesTable.childNodes;
802     for (var i = 0; i < rows.length; i++)
803         rows[i].removeStyleClass("current");
804     this.addStyleClass("current");
805 }
806
807 function loadFile(fileIndex, manageNavLists)
808 {
809     var file = files[fileIndex];
810     if (!file)
811         return;
812
813     if (currentFile != -1 && files[currentFile] && files[currentFile].element)
814         files[currentFile].element.style.display = "none";
815
816     if (!file.loaded) {
817         var sourcesDocument = document.getElementById("sources").contentDocument;
818         var sourcesDiv = sourcesDocument.body;
819         var sourceDiv = sourcesDocument.createElement("div");
820         sourceDiv.id = "file" + fileIndex;
821         sourcesDiv.appendChild(sourceDiv);
822         file.element = sourceDiv;
823
824         var table = sourcesDocument.createElement("table");
825         sourceDiv.appendChild(table);
826
827         var normalizedSource = file.source.replace(/\r\n|\r/, "\n"); // normalize line endings
828         var lines = syntaxHighlight(normalizedSource).split("\n");
829         for( var i = 0; i < lines.length; i++ ) {
830             var tr = sourcesDocument.createElement("tr");
831             var td = sourcesDocument.createElement("td");
832             td.className = "gutter";
833             td.title = (i + 1);
834             td.addEventListener("click", breakpointAction, true);
835             td.addEventListener("dblclick", toggleBreakpointEditor, true);
836             td.addEventListener("mousedown", moveBreakPoint, true);
837             tr.appendChild(td);
838
839             td = sourcesDocument.createElement("td");
840             td.className = "source";
841             td.innerHTML = (lines[i].length ? lines[i] : "&nbsp;");
842             tr.appendChild(td);
843             table.appendChild(tr);
844         }
845
846         file.loaded = true;
847     }
848
849     file.element.style.display = null;
850
851     document.getElementById("filesPopupButtonContent").innerText = (file.url ? file.url : "(unknown script)");
852     
853     var filesSelect = document.getElementById("files");
854     for (var i = 0; i < filesSelect.childNodes.length; i++) {
855         if (filesSelect.childNodes[i].value == fileIndex) {
856             filesSelect.selectedIndex = i;
857             break;
858         }
859     }
860     
861     
862     if (manageNavLists) {
863         nextFiles = new Array();
864         if (currentFile != -1)
865             previousFiles.push(currentFile);
866     }
867
868     document.getElementById("navFileLeftButton").disabled = (previousFiles.length == 0);
869     document.getElementById("navFileRightButton").disabled = (nextFiles.length == 0);
870
871     currentFile = fileIndex;
872 }
873
874 function updateFileSource(source, url, force)
875 {
876     var fileIndex = filesLookup[url];
877     if (!fileIndex || !source.length)
878         return;
879
880     var file = files[fileIndex];
881     if (force || file.source.length != source.length || file.source != source) {
882         file.source = source;
883         file.loaded = false;
884
885         if (file.element) {
886             file.element.parentNode.removeChild(file.element);
887             file.element = null;
888         }
889
890         if (currentFile == fileIndex)
891             loadFile(fileIndex, false);
892     }
893 }
894
895 /**
896 * ParsedURL - this object chops up full URL into two parts: 
897  * 1) The domain: everything from http:// to the end of the domain name
898  * 2) The relative path: everything after the domain
899  *
900  * @param string url URL to be processed
901  */
902 function ParsedURL(url)
903 {
904     // Since we're getting the URL from the browser, we're safe to assume the URL is already well formatted
905     // and so there is no need for more sophisticated regular expression here
906     var url_parts = url.match(/(http[s]?:\/\/(www)?\.?(\w|\.|-)+\w(:\d{1,5})?)\/?(.*)/);
907     
908     // the domain here is considered the whole http://www.example.org:8000 string for display purposes
909     this.domain = url_parts[1];
910     // the relative path is everything following the domain
911     this.relativePath = "/" + url_parts[5];
912 }
913
914 /**
915 * SiteBrowser - modifies the file tree via DOM as new files are being open
916  *
917  */
918 function SiteBrowser()
919 {
920     var fileBrowser = document.getElementById("filesBrowserSites");
921     
922     this.addURL = function add(url, fileIndex)
923     {
924         var parsedURL = new ParsedURL(url);
925         var divs = fileBrowser.getElementsByTagName("div");
926         
927         if (divs.length == 0) { 
928             addNewDomain(parsedURL, fileIndex);
929         } else {
930             var isNew = true;
931             for (var i = 0; i < divs.length; i++) {
932                 if (divs[i].id == parsedURL.domain) {
933                     var uls = divs[i].getElementsByTagName("ul");
934                     var ul = (uls.length > 0) ? uls[0] : document.createElement("ul");
935                     var li = document.createElement("li");
936                     
937                     li.id = fileIndex;
938                     li.addEventListener("click", fileBrowserMouseEvents, false);
939                     li.title = li.innerText = parsedURL.relativePath ? parsedURL.relativePath : "/";
940                     ul.appendChild(li);
941                     isNew = false;
942                     break;
943                 }
944             }
945             if (isNew) {
946                 addNewDomain(parsedURL, fileIndex);
947             }
948         }
949     }
950     
951     this.selectInitialFile = function sf()
952     {
953         if (currentFile == -1)
954             document.getElementById("1").className = "active";
955     }
956     
957     function addNewDomain(parsedURL, fileIndex)
958     {
959         var div = document.createElement("div");
960         var ul = document.createElement("ul");
961         var li = document.createElement("li");
962         
963         div.id = div.innerText = div.title = parsedURL.domain;
964         div.addEventListener("click", fileBrowserMouseEvents, false);
965         // Maybe we can add some roll-overs here...
966         //div.addEventListener("mouseover", fileBrowserMouseEvents, false);
967         //div.addEventListener("mouseout", fileBrowserMouseEvents, false);
968         li.id = fileIndex;
969         li.addEventListener("click", fileBrowserMouseEvents, false);
970         li.title = li.innerText = parsedURL.relativePath ? parsedURL.relativePath : "/";
971         ul.appendChild(li);
972         div.appendChild(ul);
973         fileBrowser.appendChild(div);        
974     }
975 }
976
977 function fileBrowserMouseEvents(event)
978 {
979     switch (event.type)
980     {
981         case "click":
982             // If we clicked on a site, collapse/expand it, if on a file, display it. Since we're only capturing this
983             // event from either a DIV or LI element, we don't have to worry about any ambiguity
984             (event.target.nodeName.toUpperCase() == "DIV") ? toggleCollapseSite(event) : fileClicked(event.target.id);
985             break;
986     }
987 }
988
989 function fileClicked(fileId, shouldLoadFile)
990 {
991     if (shouldLoadFile === undefined) shouldLoadFile = true;
992     
993     document.getElementById(currentFile).className = "passive";
994     document.getElementById(fileId).className = "active";
995     if (shouldLoadFile) 
996         loadFile(fileId, false);
997 }
998
999 function toggleCollapseSite(event)
1000 {
1001     var thisSite = document.getElementById(event.target.id);
1002     var siteFiles = thisSite.getElementsByTagName("ul");
1003     
1004     if (siteFiles[0].style.display == "block" || !siteFiles[0].style.display) {
1005         siteFiles[0].style.display = "none";
1006         thisSite.className = "collapsed"; 
1007     } else {
1008         siteFiles[0].style.display = "block";
1009         thisSite.className = "expanded";
1010     }
1011 }
1012
1013 function didParseScript(source, fileSource, url, sourceId, baseLineNumber)
1014 {
1015     var fileIndex = filesLookup[url];
1016     var file = files[fileIndex];
1017     var firstLoad = false;
1018
1019     if (!fileIndex || !file) {
1020         fileIndex = files.length + 1;
1021         if (url.length)
1022             filesLookup[url] = fileIndex;
1023
1024         file = new Object();
1025         file.scripts = new Array();
1026         file.breakpoints = new Array();
1027         file.disabledBreakpoints = new Array();
1028         file.breakpointEditors = new Array();
1029         file.source = (fileSource.length ? fileSource : source);
1030         file.url = (url.length ? url : null);
1031         file.loaded = false;
1032
1033         files[fileIndex] = file;
1034
1035         var filesSelect = document.getElementById("files");
1036         var option = document.createElement("option");
1037         files[fileIndex].menuOption = option;
1038         option.value = fileIndex;
1039         option.text = (file.url ? file.url : "(unknown script)");
1040         filesSelect.appendChild(option);
1041
1042         var siteBrowser = new SiteBrowser();
1043         siteBrowser.addURL(file.url, fileIndex);
1044         siteBrowser.selectInitialFile();        
1045         
1046         firstLoad = true;
1047     }
1048
1049     var sourceObj = new Object();
1050     sourceObj.file = fileIndex;
1051     sourceObj.baseLineNumber = baseLineNumber;
1052     file.scripts.push(sourceId);
1053     scripts[sourceId] = sourceObj;
1054
1055     if (!firstLoad)
1056         updateFileSource((fileSource.length ? fileSource : source), url, false);
1057
1058     if (currentFile == -1)
1059         loadFile(fileIndex, false);
1060 }
1061
1062 function jumpToLine(sourceId, line)
1063 {
1064     var script = scripts[sourceId];
1065     if (line <= 0 || !script)
1066         return;
1067
1068     var file = files[script.file];
1069     if (!file)
1070         return;
1071
1072     if (currentFile != script.file)
1073         loadFile(script.file, true);
1074     if (currentRow)
1075         currentRow.removeStyleClass("current");
1076     if (!file.element)
1077         return;
1078     if (line > file.element.firstChild.childNodes.length)
1079         return;
1080
1081     currentRow = file.element.firstChild.childNodes.item(line - 1);
1082     if (!currentRow)
1083         return;
1084
1085     currentRow.addStyleClass("current");
1086
1087     var sourcesDiv = document.getElementById("sources");
1088     var sourcesDocument = document.getElementById("sources").contentDocument;
1089     var parent = sourcesDocument.body;
1090     var offset = totalOffsetTop(currentRow, parent);
1091     if (offset < (parent.scrollTop + 20) || offset > (parent.scrollTop + sourcesDiv.clientHeight - 20))
1092         parent.scrollTop = totalOffsetTop(currentRow, parent) - (sourcesDiv.clientHeight / 2) + 10;
1093 }
1094
1095 function willExecuteStatement(sourceId, line, fromLeavingFrame)
1096 {
1097     var script = scripts[sourceId];
1098     if (line <= 0 || !script)
1099         return;
1100
1101     var file = files[script.file];
1102     if (!file)
1103         return;
1104
1105     lastStatement = [sourceId, line];
1106     
1107     var breakpoint = file.breakpoints[line];
1108     var shouldBreak = breakpoint && (breakpoint == "break" || DebuggerDocument.evaluateScript_inCallFrame_(breakpoint, 0) == 1);
1109     
1110     if (pauseOnNextStatement || shouldBreak || (steppingOver && !steppingStack)) {
1111         pause();
1112         pauseOnNextStatement = false;
1113         pausedWhileLeavingFrame = fromLeavingFrame || false;
1114     }
1115
1116     if (isPaused()) {
1117         updateFunctionStack();
1118         jumpToLine(sourceId, line);
1119     }
1120 }
1121
1122 function didEnterCallFrame(sourceId, line)
1123 {
1124     if (steppingOver || steppingOut)
1125         steppingStack++;
1126
1127     if (lastStatement)
1128         frameLineNumberStack.unshift(lastStatement);
1129     willExecuteStatement(sourceId, line);
1130 }
1131
1132 function willLeaveCallFrame(sourceId, line)
1133 {
1134     if (line <= 0)
1135         resume();
1136     willExecuteStatement(sourceId, line, true);
1137     frameLineNumberStack.shift();
1138     if (!steppingStack)
1139         steppingOver = false;
1140     if (steppingOut && !steppingStack) {
1141         steppingOut = false;
1142         pauseOnNextStatement = true;
1143     }
1144     if ((steppingOver || steppingOut) && steppingStack >= 1)
1145         steppingStack--;
1146 }
1147
1148 function exceptionWasRaised(sourceId, line)
1149 {
1150     pause();
1151     updateFunctionStack();
1152     jumpToLine(sourceId, line);
1153 }
1154
1155 function showConsoleWindow()
1156 {
1157     if (!consoleWindow)
1158         consoleWindow = window.open("console.html", "console", "top=200, left=200, width=500, height=300, toolbar=yes, resizable=yes");
1159 }