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)
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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.
31 var files = new Array();
32 var filesLookup = new Object();
33 var scripts = new Array();
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;
53 ScriptCallFrame = function (functionName, index, row)
55 this.functionName = functionName;
58 this.localVariableNames = null;
61 ScriptCallFrame.prototype.valueForScopeVariable = function (name)
63 return DebuggerDocument.valueForScopeVariableNamed_inCallFrame_(name, this.index);
66 ScriptCallFrame.prototype.loadVariables = function ()
68 if (!this.localVariableNames)
69 this.localVariableNames = DebuggerDocument.localScopeVariableNamesForCallFrame_(this.index);
71 var variablesTable = document.getElementById("variablesTable");
72 variablesTable.innerHTML = "";
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";
81 td = document.createElement("td");
82 td.innerText = this.valueForScopeVariable(this.localVariableNames[i]);
84 tr.addEventListener("click", selectVariable, true);
86 variablesTable.appendChild(tr);
90 function sleep(numberMillis)
93 var exitTime = now.getTime() + numberMillis;
96 if (now.getTime() > exitTime)
101 function headerMouseDown(element)
103 if (!isResizingColumn)
104 element.style.background = "url(glossyHeaderPressed.png) repeat-x";
107 function headerMouseUp(element)
109 element.style.background = "url(glossyHeader.png) repeat-x";
112 function headerMouseOut(element)
114 element.style.background = "url(glossyHeader.png) repeat-x";
117 function filesDividerDragStart(event)
119 dividerDragStart(document.getElementById("filesDivider"), filesDividerDrag, filesDividerDragEnd, event, "col-resize");
122 function filesDividerDragEnd(event)
124 dividerDragEnd(document.getElementById("filesDivider"), filesDividerDrag, filesDividerDragEnd, event);
127 function filesDividerDrag(event)
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();
145 function dividerDragStart(element, dividerDrag, dividerDragEnd, event, cursor)
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();
156 function dividerDragEnd(element, dividerDrag, dividerDragEnd, event)
158 element.dragging = false;
159 document.removeEventListener("mousemove", dividerDrag, true);
160 document.removeEventListener("mouseup", dividerDragEnd, true);
161 document.body.style.cursor = null;
164 function dividerDrag(event)
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();
182 function sourceDividerDragStart(event)
184 dividerDragStart(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event, "row-resize");
187 function sourceDividerDragEnd(event)
189 dividerDragEnd(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event);
192 function infoDividerDragStart(event)
194 dividerDragStart(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event, "col-resize");
197 function infoDividerDragEnd(event)
199 dividerDragEnd(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event);
202 function infoDividerDrag(event)
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();
220 function columnResizerDragStart(event)
222 isResizingColumn = true;
223 dividerDragStart(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event, "col-resize");
226 function columnResizerDragEnd(event)
228 isResizingColumn = false;
229 dividerDragEnd(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
232 function columnResizerDrag(event)
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];
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();
257 function constrainedWidthFromElement(width, element, constrainLeft, constrainRight)
259 if (constrainLeft === undefined) constrainLeft = 0.25;
260 if (constrainRight === undefined) constrainRight = 0.75;
262 if (width < element.clientWidth * constrainLeft)
263 width = element.clientWidth * constrainLeft;
264 else if (width > element.clientWidth * constrainRight)
265 width = element.clientWidth * constrainRight;
269 function constrainedHeightFromElement(height, element)
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;
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);
288 return DebuggerDocument.isPaused();
293 DebuggerDocument.pause();
299 currentRow.removeStyleClass("current");
303 var stackframeTable = document.getElementById("stackframeTable");
304 stackframeTable.innerHTML = ""; // clear the content
305 var variablesTable = document.getElementById("variablesTable");
306 variablesTable.innerHTML = ""; // clear the content
308 currentCallFrame = null;
310 pauseOnNextStatement = false;
311 pausedWhileLeavingFrame = false;
313 steppingOver = false;
316 DebuggerDocument.resume();
321 pauseOnNextStatement = false;
323 steppingOver = false;
325 DebuggerDocument.stepInto();
330 pauseOnNextStatement = false;
333 DebuggerDocument.resume();
338 pauseOnNextStatement = pausedWhileLeavingFrame;
339 steppingOver = false;
342 DebuggerDocument.resume();
345 Element.prototype.removeStyleClass = function(className)
347 if (this.hasStyleClass(className))
348 this.className = this.className.replace(className, "");
351 Element.prototype.addStyleClass = function(className)
353 if (!this.hasStyleClass(className))
354 this.className += (this.className.length ? " " + className : className);
357 Element.prototype.hasStyleClass = function(className)
359 return this.className.indexOf(className) != -1;
362 Element.prototype.firstParentWithClass = function(className)
364 var node = this.parentNode;
365 while(!node.hasStyleClass(className)) {
366 if (node == document)
368 node = node.parentNode;
373 Element.prototype.query = function(query)
375 return document.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
378 function breakpointAction(event)
380 var file = files[currentFile];
381 var lineNum = event.target.title;
383 if (file.breakpoints[lineNum]) {
385 pendingAction = setTimeout(toggleBreakpointOnLine, DebuggerDocument.doubleClickMilliseconds(), lineNum);
387 file.breakpoints[lineNum] = new BreakPoint(event.target.parentNode, file, lineNum);
390 BreakPoint = function(row, file, line)
395 row.addStyleClass("breakpoint");
396 row.removeStyleClass("disabled");
397 this.value = "break";
404 function toggleBreakpointEditorOnLine(lineNum)
407 clearTimeout(pendingAction);
408 pendingAction = null;
410 var file = files[currentFile];
411 bp = file.breakpoints[lineNum];
413 var editor = bp.editor;
415 var sourcesDocument = document.getElementById("sources").contentDocument;
416 editor = sourcesDocument.createElement("div");
417 editor.className = "editor";
419 editor.innerHTML = breakpointEditorHTML;
421 bp.row.childNodes[1].appendChild(editor);
424 file.breakpoints[lineNum] = bp;
426 editor.query('//input[@class="enable"]').checked = bp.enabled;
428 editor.query('//select[@class="editorDropdown"]').selectedIndex = bp.type;
429 updateBreakpointTypeOnLine(lineNum);
431 editor.query('//span[@class="hitCounter"]').innerText = bp.hitcount;
433 setConditionFieldText(bp);
435 saveBreakpointOnLine(lineNum);
436 bp.row.childNodes[1].removeChild(editor);
442 function updateBreakpointTypeOnLine(line)
444 var breakpoint = files[currentFile].breakpoints[line];
445 var editor = breakpoint.editor;
446 var label = editor.query('//label[@class="conditionLabel"]');
447 var dropdown = editor.query('//select[@class="editorDropdown"]');
448 breakpoint.type = dropdown.selectedIndex;
449 switch(breakpoint.type) {
451 label.innerText = "Condition:";
454 label.innerText = "Expression:";
459 function setConditionFieldText(breakpoint)
461 var conditionField = breakpoint.editor.query('//div[@class="condition"]');
463 var functionBody = breakpoint.value;
464 if (!functionBody || functionBody == "break")
467 var startIndex = functionBody.indexOf("return((") + 8;
469 if (startIndex != 7) //-1 + 8, yes, that's lame
470 endIndex = functionBody.lastIndexOf("))");
472 startIndex = functionBody.indexOf("{") + 1;
473 endIndex = functionBody.lastIndexOf("}");
475 functionBody = functionBody.substring(startIndex, endIndex);
477 conditionField.innerText = functionBody;
478 conditionField.addEventListener("keyup", new Function("saveBreakpointOnLine(" + breakpoint.line + ");"), false);
479 conditionField.focus();
482 function saveBreakpointOnLine(lineNum)
484 var file = files[currentFile];
485 var breakpoint = file.breakpoints[lineNum];
486 row = file.element.firstChild.childNodes.item(lineNum - 1);
487 var editor = breakpoint.editor;
488 var body = editor.query('//div[@class="condition"]').innerText;
489 var actionIndex = editor.query('//select[@class="editorDropdown"]').selectedIndex;
490 if (body.length == 0)
491 breakpoint.value = "break";
492 else if (body.indexOf("return") != -1)
493 breakpoint.value = "__drosera_breakpoint_conditional_func = function() {" + body + "}; __drosera_breakpoint_conditional_func();";
495 breakpoint.value = "__drosera_breakpoint_conditional_func = function() { return((" + body + ")); }; __drosera_breakpoint_conditional_func();";
498 function toggleBreakpointOnLine(lineNum)
500 var breakpoint = files[currentFile].breakpoints[lineNum];
501 pendingAction = null;
502 if (breakpoint.enabled)
503 breakpoint.row.addStyleClass("disabled");
505 breakpoint.row.removeStyleClass("disabled");
507 var hack = breakpoint.row.offsetTop; // force a relayout if needed.
509 breakpoint.enabled = !breakpoint.enabled;
510 var editor = breakpoint.editor;
512 editor.query('//input[@class="enable"]').checked = breakpoint.enabled;
513 setConditionFieldText(editor, lineNum);
517 function moveBreakPoint(event)
519 if (event.target.parentNode.hasStyleClass("breakpoint")) {
520 draggingBreakpoint = event.target;
521 draggingBreakpoint.started = false;
522 draggingBreakpoint.dragLastY = event.clientY + window.scrollY;
523 draggingBreakpoint.dragLastX = event.clientX + window.scrollX;
524 var sourcesDocument = document.getElementById("sources").contentDocument;
525 sourcesDocument.addEventListener("mousemove", breakpointDrag, true);
526 sourcesDocument.addEventListener("mouseup", breakpointDragEnd, true);
527 sourcesDocument.body.style.cursor = "default";
531 function breakpointDrag(event)
533 var sourcesDocument = document.getElementById("sources").contentDocument;
534 if (!draggingBreakpoint) {
535 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
536 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
537 sourcesDocument.body.style.cursor = null;
541 var x = event.clientX + window.scrollX;
542 var y = event.clientY + window.scrollY;
543 var deltaX = draggingBreakpoint.dragLastX - x;
544 var deltaY = draggingBreakpoint.dragLastY - y;
545 if (draggingBreakpoint.started || deltaX > 4 || deltaY > 4 || deltaX < -4 || deltaY < -4) {
547 if (!draggingBreakpoint.started) {
548 var lineNum = draggingBreakpoint.title;
549 var file = files[currentFile];
550 var breakpoint = file.breakpoints[lineNum];
551 draggingBreakpoint.breakpoint = breakpoint;
552 breakpoint.row.removeStyleClass("breakpoint");
553 breakpoint.row.removeStyleClass("disabled");
555 var editor = breakpoint.editor;
557 toggleBreakpointEditorOnLine(lineNum);
559 draggingBreakpoint.started = true;
561 file.breakpoints[lineNum] = null;
563 var dragImage = sourcesDocument.createElement("img");
564 if (draggingBreakpoint.breakpoint.enabled)
565 dragImage.src = "breakPoint.tif";
567 dragImage.src = "breakPointDisabled.tif";
569 dragImage.id = "breakpointDrag";
570 dragImage.style.top = y - 8 + "px";
571 dragImage.style.left = x - 12 + "px";
572 sourcesDocument.body.appendChild(dragImage);
574 var dragImage = sourcesDocument.getElementById("breakpointDrag");
576 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
577 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
578 sourcesDocument.body.style.cursor = null;
582 dragImage.style.top = y - 8 + "px";
583 dragImage.style.left = x - 12 + "px";
585 dragImage.style.visibility = "hidden";
587 dragImage.style.visibility = null;
590 draggingBreakpoint.dragLastX = x;
591 draggingBreakpoint.dragLastY = y;
595 function breakpointDragEnd(event)
597 var sourcesDocument = document.getElementById("sources").contentDocument;
598 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
599 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
600 sourcesDocument.body.style.cursor = null;
602 var dragImage = sourcesDocument.getElementById("breakpointDrag");
606 dragImage.parentNode.removeChild(dragImage);
608 var x = event.clientX + window.scrollX;
609 if (x > 40 || !draggingBreakpoint)
612 var y = event.clientY + window.scrollY;
613 var rowHeight = draggingBreakpoint.parentNode.offsetHeight;
614 var row = Math.ceil(y / rowHeight);
618 var file = files[currentFile];
619 var table = file.element.firstChild;
620 if (row > table.childNodes.length)
623 var tr = table.childNodes.item(row - 1);
627 var breakpoint = draggingBreakpoint.breakpoint;
630 // leave the editor there if it exists... we'll want to update it to the new values
631 breakpoint.editor = file.breakpoints[row].editor;
633 file.breakpoints[row] = breakpoint;
635 if (breakpoint.editor) {
636 breakpoint.editor.id = row;
637 updateBreakpointTypeOnLine(row);
638 setConditionFieldText(breakpoint);
641 if (!breakpoint.enabled)
642 tr.addStyleClass("disabled");
644 tr.addStyleClass("breakpoint");
646 draggingBreakpoint = null;
649 function totalOffsetTop(element, stop)
652 while (element.offsetParent) {
653 currentTop += element.offsetTop
654 element = element.offsetParent;
661 function switchFile()
663 var filesSelect = document.getElementById("files");
664 fileClicked(filesSelect.options[filesSelect.selectedIndex].value, false);
665 loadFile(filesSelect.options[filesSelect.selectedIndex].value, true);
668 function syntaxHighlight(code)
670 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 };
672 function echoChar(c) {
685 function isDigit(number) {
686 var string = "1234567890";
687 if (string.indexOf(number) != -1)
692 function isHex(hex) {
693 var string = "1234567890abcdefABCDEF";
694 if (string.indexOf(hex) != -1)
699 function isLetter(letter) {
700 var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
701 if (string.indexOf(letter) != -1)
710 for (var i = 0; i < code.length; i++) {
713 cNext = code.charAt(i + 1);
715 if (c == "/" && cNext == "*") {
716 result += "<span class=\"comment\">";
719 for (i += 2; i < code.length; i++) {
725 result += "<span class=\"comment\">";
726 if (cPrev == "*" && c == "/")
732 } else if (c == "/" && cNext == "/") {
733 result += "<span class=\"comment\">";
736 for (i += 2; i < code.length; i++) {
745 } else if (c == "\"" || c == "'") {
746 var instringtype = c;
748 result += "<span class=\"string\">";
750 for (i += 1; i < code.length; i++) {
752 if (stringstart < (i - 1) && cPrev == instringtype && code.charAt(i - 2) != "\\")
760 } else if (c == "0" && cNext == "x" && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
761 result += "<span class=\"number\">";
764 for (i += 2; i < code.length; i++) {
773 } else if ((isDigit(c) || ((c == "-" || c == ".") && isDigit(cNext))) && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
774 result += "<span class=\"number\">";
776 for (i += 1; i < code.length; i++) {
778 if (!isDigit(c) && c != ".")
785 } else if (isLetter(c) && (i == 0 || !isLetter(cPrev))) {
788 for (var j = i + 1; j < i + 12 && j < code.length; j++) {
795 if (keywords[keyword]) {
796 result += "<span class=\"keyword\">" + keyword + "</span>";
797 i += keyword.length - 1;
808 function navFilePrevious(element)
810 if (element.disabled)
812 var lastFile = previousFiles.pop();
813 if (currentFile != -1)
814 nextFiles.unshift(currentFile);
815 loadFile(lastFile, false);
818 function navFileNext(element)
820 if (element.disabled)
822 var lastFile = nextFiles.shift();
823 if (currentFile != -1)
824 previousFiles.push(currentFile);
825 loadFile(lastFile, false);
828 function updateFunctionStack()
830 var stackframeTable = document.getElementById("stackframeTable");
831 stackframeTable.innerHTML = ""; // clear the content
833 currentStack = new Array();
834 var stack = DebuggerDocument.currentFunctionStack();
835 for(var i = 0; i < stack.length; i++) {
836 var tr = document.createElement("tr");
837 var td = document.createElement("td");
838 td.className = "stackNumber";
842 td = document.createElement("td");
843 td.innerText = stack[i];
845 tr.addEventListener("click", selectStackFrame, true);
847 stackframeTable.appendChild(tr);
849 var frame = new ScriptCallFrame(stack[i], i, tr);
850 tr.callFrame = frame;
851 currentStack.push(frame);
854 tr.addStyleClass("current");
855 frame.loadVariables();
856 currentCallFrame = frame;
861 function selectStackFrame(event)
863 var stackframeTable = document.getElementById("stackframeTable");
864 var rows = stackframeTable.childNodes;
865 for (var i = 0; i < rows.length; i++)
866 rows[i].removeStyleClass("current");
867 this.addStyleClass("current");
868 this.callFrame.loadVariables();
869 currentCallFrame = this.callFrame;
871 if (frameLineNumberInfo = frameLineNumberStack[this.callFrame.index - 1])
872 jumpToLine(frameLineNumberInfo[0], frameLineNumberInfo[1]);
873 else if (this.callFrame.index == 0)
874 jumpToLine(lastStatement[0], lastStatement[1]);
877 function selectVariable(event)
879 var variablesTable = document.getElementById("variablesTable");
880 var rows = variablesTable.childNodes;
881 for (var i = 0; i < rows.length; i++)
882 rows[i].removeStyleClass("current");
883 this.addStyleClass("current");
886 function loadFile(fileIndex, manageNavLists)
888 var file = files[fileIndex];
892 if (currentFile != -1 && files[currentFile] && files[currentFile].element)
893 files[currentFile].element.style.display = "none";
896 var sourcesDocument = document.getElementById("sources").contentDocument;
897 var sourcesDiv = sourcesDocument.body;
898 var sourceDiv = sourcesDocument.createElement("div");
899 sourceDiv.id = "file" + fileIndex;
900 sourcesDiv.appendChild(sourceDiv);
901 file.element = sourceDiv;
903 var table = sourcesDocument.createElement("table");
904 sourceDiv.appendChild(table);
906 var normalizedSource = file.source.replace(/\r\n|\r/, "\n"); // normalize line endings
907 var lines = syntaxHighlight(normalizedSource).split("\n");
908 for( var i = 0; i < lines.length; i++ ) {
909 var tr = sourcesDocument.createElement("tr");
910 var td = sourcesDocument.createElement("td");
911 td.className = "gutter";
913 td.addEventListener("click", breakpointAction, true);
914 td.addEventListener("dblclick", function() { toggleBreakpointEditorOnLine(event.target.title); }, true);
915 td.addEventListener("mousedown", moveBreakPoint, true);
918 td = sourcesDocument.createElement("td");
919 td.className = "source";
920 td.innerHTML = (lines[i].length ? lines[i] : " ");
922 table.appendChild(tr);
928 file.element.style.display = null;
930 document.getElementById("filesPopupButtonContent").innerText = (file.url ? file.url : "(unknown script)");
932 var filesSelect = document.getElementById("files");
933 for (var i = 0; i < filesSelect.childNodes.length; i++) {
934 if (filesSelect.childNodes[i].value == fileIndex) {
935 filesSelect.selectedIndex = i;
941 if (manageNavLists) {
942 nextFiles = new Array();
943 if (currentFile != -1)
944 previousFiles.push(currentFile);
947 document.getElementById("navFileLeftButton").disabled = (previousFiles.length == 0);
948 document.getElementById("navFileRightButton").disabled = (nextFiles.length == 0);
950 currentFile = fileIndex;
953 function updateFileSource(source, url, force)
955 var fileIndex = filesLookup[url];
956 if (!fileIndex || !source.length)
959 var file = files[fileIndex];
960 if (force || file.source.length != source.length || file.source != source) {
961 file.source = source;
965 file.element.parentNode.removeChild(file.element);
969 if (currentFile == fileIndex)
970 loadFile(fileIndex, false);
975 * ParsedURL - this object chops up full URL into two parts:
976 * 1) The domain: everything from http:// to the end of the domain name
977 * 2) The relative path: everything after the domain
979 * @param string url URL to be processed
981 function ParsedURL(url)
983 // Since we're getting the URL from the browser, we're safe to assume the URL is already well formatted
984 // and so there is no need for more sophisticated regular expression here
985 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})?)\/?(.*)/);
986 // the domain here is considered the whole http://www.example.org:8000 or file:///Users/user/folder/file.htm string for display purposes
987 this.domain = url_parts[1];
988 // the relative path is everything following the domain
989 this.relativePath = (url_parts[5] === undefined) ? "/" + url_parts[3] : "/" + url_parts[5];
993 * SiteBrowser - modifies the file tree via DOM as new files are being open
996 function SiteBrowser()
998 var fileBrowser = document.getElementById("filesBrowserSites");
1000 this.addURL = function add(url, fileIndex)
1002 var parsedURL = new ParsedURL(url);
1003 var divs = fileBrowser.getElementsByTagName("div");
1005 if (divs.length == 0) {
1006 addNewDomain(parsedURL, fileIndex);
1009 for (var i = 0; i < divs.length; i++) {
1010 if (divs[i].id == parsedURL.domain) {
1011 var uls = divs[i].getElementsByTagName("ul");
1012 var ul = (uls.length > 0) ? uls[0] : document.createElement("ul");
1013 var li = document.createElement("li");
1016 li.addEventListener("click", fileBrowserMouseEvents, false);
1017 li.title = li.innerText = parsedURL.relativePath ? parsedURL.relativePath : "/";
1024 addNewDomain(parsedURL, fileIndex);
1029 this.selectInitialFile = function sf()
1031 if (currentFile == -1)
1032 document.getElementById("1").className = "active";
1035 function addNewDomain(parsedURL, fileIndex)
1037 var div = document.createElement("div");
1038 var ul = document.createElement("ul");
1039 var li = document.createElement("li");
1041 div.id = div.innerText = div.title = parsedURL.domain;
1042 div.addEventListener("click", fileBrowserMouseEvents, false);
1043 // Maybe we can add some roll-overs here...
1044 //div.addEventListener("mouseover", fileBrowserMouseEvents, false);
1045 //div.addEventListener("mouseout", fileBrowserMouseEvents, false);
1047 li.addEventListener("click", fileBrowserMouseEvents, false);
1048 li.title = li.innerText = parsedURL.relativePath ? parsedURL.relativePath : "/";
1050 div.appendChild(ul);
1051 fileBrowser.appendChild(div);
1055 function fileBrowserMouseEvents(event)
1060 // If we clicked on a site, collapse/expand it, if on a file, display it. Since we're only capturing this
1061 // event from either a DIV or LI element, we don't have to worry about any ambiguity
1062 (event.target.nodeName.toUpperCase() == "DIV") ? toggleCollapseSite(event) : fileClicked(event.target.id);
1067 function fileClicked(fileId, shouldLoadFile)
1069 if (shouldLoadFile === undefined) shouldLoadFile = true;
1071 document.getElementById(currentFile).className = "passive";
1072 document.getElementById(fileId).className = "active";
1074 loadFile(fileId, false);
1077 function toggleCollapseSite(event)
1079 var thisSite = document.getElementById(event.target.id);
1080 var siteFiles = thisSite.getElementsByTagName("ul");
1082 if (siteFiles[0].style.display == "block" || !siteFiles[0].style.display) {
1083 siteFiles[0].style.display = "none";
1084 thisSite.className = "collapsed";
1086 siteFiles[0].style.display = "block";
1087 thisSite.className = "expanded";
1091 function didParseScript(source, fileSource, url, sourceId, baseLineNumber)
1093 var fileIndex = filesLookup[url];
1094 var file = files[fileIndex];
1095 var firstLoad = false;
1097 if (!fileIndex || !file) {
1098 fileIndex = files.length + 1;
1100 filesLookup[url] = fileIndex;
1102 file = new Object();
1103 file.scripts = new Array();
1104 file.breakpoints = new Array();
1105 file.source = (fileSource.length ? fileSource : source);
1106 file.url = (url.length ? url : null);
1107 file.loaded = false;
1109 files[fileIndex] = file;
1111 var filesSelect = document.getElementById("files");
1112 var option = document.createElement("option");
1113 files[fileIndex].menuOption = option;
1114 option.value = fileIndex;
1115 option.text = (file.url ? file.url : "(unknown script)");
1116 filesSelect.appendChild(option);
1118 var siteBrowser = new SiteBrowser();
1119 siteBrowser.addURL(file.url, fileIndex);
1120 siteBrowser.selectInitialFile();
1125 var sourceObj = new Object();
1126 sourceObj.file = fileIndex;
1127 sourceObj.baseLineNumber = baseLineNumber;
1128 file.scripts.push(sourceId);
1129 scripts[sourceId] = sourceObj;
1132 updateFileSource((fileSource.length ? fileSource : source), url, false);
1134 if (currentFile == -1)
1135 loadFile(fileIndex, false);
1138 function jumpToLine(sourceId, line)
1140 var script = scripts[sourceId];
1141 if (line <= 0 || !script)
1144 var file = files[script.file];
1148 if (currentFile != script.file)
1149 loadFile(script.file, true);
1151 currentRow.removeStyleClass("current");
1154 if (line > file.element.firstChild.childNodes.length)
1157 currentRow = file.element.firstChild.childNodes.item(line - 1);
1161 currentRow.addStyleClass("current");
1163 var sourcesDiv = document.getElementById("sources");
1164 var sourcesDocument = document.getElementById("sources").contentDocument;
1165 var parent = sourcesDocument.body;
1166 var offset = totalOffsetTop(currentRow, parent);
1167 if (offset < (parent.scrollTop + 20) || offset > (parent.scrollTop + sourcesDiv.clientHeight - 20))
1168 parent.scrollTop = totalOffsetTop(currentRow, parent) - (sourcesDiv.clientHeight / 2) + 10;
1171 function willExecuteStatement(sourceId, line, fromLeavingFrame)
1173 var script = scripts[sourceId];
1174 if (line <= 0 || !script)
1177 var file = files[script.file];
1181 lastStatement = [sourceId, line];
1183 var breakpoint = file.breakpoints[line];
1185 var shouldBreak = false;
1187 if (breakpoint && breakpoint.enabled) {
1188 switch(breakpoint.type) {
1190 shouldBreak = (breakpoint.value == "break" || DebuggerDocument.evaluateScript_inCallFrame_(breakpoint.value, 0) == 1);
1192 breakpoint.hitcount++;
1195 var message = "Hit breakpoint on line " + line;
1196 if (breakpoint.value != "break")
1197 message = DebuggerDocument.evaluateScript_inCallFrame_(breakpoint.value, 0);
1199 consoleWindow.appendMessage("", message);
1200 breakpoint.hitcount++;
1203 var editor = breakpoint.editor;
1206 counter = breakpoint.editor.query('//span[@class="hitCounter"]');
1208 counter.innerText = breakpoint.hitcount;
1211 if (pauseOnNextStatement || shouldBreak || (steppingOver && !steppingStack)) {
1213 pauseOnNextStatement = false;
1214 pausedWhileLeavingFrame = fromLeavingFrame || false;
1218 updateFunctionStack();
1219 jumpToLine(sourceId, line);
1223 function didEnterCallFrame(sourceId, line)
1225 if (steppingOver || steppingOut)
1229 frameLineNumberStack.unshift(lastStatement);
1230 willExecuteStatement(sourceId, line);
1233 function willLeaveCallFrame(sourceId, line)
1237 willExecuteStatement(sourceId, line, true);
1238 frameLineNumberStack.shift();
1240 steppingOver = false;
1241 if (steppingOut && !steppingStack) {
1242 steppingOut = false;
1243 pauseOnNextStatement = true;
1245 if ((steppingOver || steppingOut) && steppingStack >= 1)
1249 function exceptionWasRaised(sourceId, line)
1252 updateFunctionStack();
1253 jumpToLine(sourceId, line);
1256 function showConsoleWindow()
1259 consoleWindow = window.open("console.html", "console", "top=200, left=200, width=500, height=300, toolbar=yes, resizable=yes");
1261 consoleWindow.focus();