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