2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 var files = new Array();
30 var filesLookup = new Object();
31 var scripts = new Array();
33 var currentRow = null;
34 var currentStack = null;
35 var currentCallFrame = null;
36 var previousFiles = new Array();
37 var nextFiles = new Array();
38 var isResizingColumn = false;
39 var draggingBreakpoint = null;
40 var steppingOut = false;
41 var steppingOver = false;
42 var steppingStack = 0;
43 var pauseOnNextStatement = false;
44 var consoleWindow = null;
46 ScriptCallFrame = function (functionName, index, row)
48 this.functionName = functionName;
51 this.localVariableNames = null;
54 ScriptCallFrame.prototype.valueForScopeVariable = function (name)
56 return DebuggerDocument.valueForScopeVariableNamed_inCallFrame_(name, this.index);
59 ScriptCallFrame.prototype.loadVariables = function ()
61 if (!this.localVariableNames)
62 this.localVariableNames = DebuggerDocument.localScopeVariableNamesForCallFrame_(this.index);
64 var variablesTable = document.getElementById("variablesTable");
65 variablesTable.innerHTML = "";
67 for(var i = 0; i < this.localVariableNames.length; i++) {
68 var tr = document.createElement("tr");
69 var td = document.createElement("td");
70 td.innerText = this.localVariableNames[i];
71 td.className = "variable";
74 td = document.createElement("td");
75 td.innerText = this.valueForScopeVariable(this.localVariableNames[i]);
77 tr.addEventListener("click", selectVariable, true);
79 variablesTable.appendChild(tr);
83 function sleep(numberMillis) {
85 var exitTime = now.getTime() + numberMillis;
88 if (now.getTime() > exitTime)
93 function headerMouseDown(element) {
94 if (!isResizingColumn)
95 element.style.background = "url(glossyHeaderPressed.png) repeat-x";
98 function headerMouseUp(element) {
99 element.style.background = "url(glossyHeader.png) repeat-x";
102 function headerMouseOut(element) {
103 element.style.background = "url(glossyHeader.png) repeat-x";
106 function dividerDragStart(element, dividerDrag, dividerDragEnd, event, cursor) {
107 element.dragging = true;
108 element.dragLastY = event.clientY + window.scrollY;
109 element.dragLastX = event.clientX + window.scrollX;
110 document.addEventListener("mousemove", dividerDrag, true);
111 document.addEventListener("mouseup", dividerDragEnd, true);
112 document.body.style.cursor = cursor;
113 event.preventDefault();
116 function sourceDividerDragStart(event) {
117 dividerDragStart(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event, "row-resize");
120 function infoDividerDragStart(event) {
121 dividerDragStart(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event, "col-resize");
124 function columnResizerDragStart(event) {
125 isResizingColumn = true;
126 dividerDragStart(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event, "col-resize");
129 function columnResizerDragEnd(event) {
130 isResizingColumn = false;
131 dividerDragEnd(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
134 function infoDividerDragEnd(event) {
135 dividerDragEnd(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event);
138 function sourceDividerDragEnd(event) {
139 dividerDragEnd(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event);
142 function dividerDragEnd(element, dividerDrag, dividerDragEnd, event) {
143 element.dragging = false;
144 document.removeEventListener("mousemove", dividerDrag, true);
145 document.removeEventListener("mouseup", dividerDragEnd, true);
146 document.body.style.cursor = null;
149 function columnResizerDrag(event) {
150 var element = document.getElementById("variableColumnResizer");
151 if (element.dragging == true) {
152 var main = document.getElementById("rightPane");
153 var variableColumn = document.getElementById("variable");
154 var rules = document.defaultView.getMatchedCSSRules(variableColumn, "");
155 for (var i = 0; i < rules.length; i++) {
156 if (rules[i].selectorText == ".variable") {
157 var columnRule = rules[i];
162 var x = event.clientX + window.scrollX;
163 var delta = element.dragLastX - x;
164 var newWidth = constrainedWidthFromElement(variableColumn.clientWidth - delta, main);
165 if ((variableColumn.clientWidth - delta) == newWidth) // the width wasn't constrained
166 element.dragLastX = x;
167 columnRule.style.width = newWidth + "px";
168 element.style.left = newWidth + "px";
169 event.preventDefault();
173 function constrainedWidthFromElement(width, element) {
174 if (width < element.clientWidth * 0.25)
175 width = element.clientWidth * 0.25;
176 else if (width > element.clientWidth * 0.75)
177 width = element.clientWidth * 0.75;
181 function constrainedHeightFromElement(height, element) {
182 if (height < element.clientHeight * 0.25)
183 height = element.clientHeight * 0.25;
184 else if (height > element.clientHeight * 0.75)
185 height = element.clientHeight * 0.75;
189 function infoDividerDrag(event) {
190 var element = document.getElementById("infoDivider");
191 if (document.getElementById("infoDivider").dragging == true) {
192 var main = document.getElementById("main");
193 var leftPane = document.getElementById("leftPane");
194 var rightPane = document.getElementById("rightPane");
195 var x = event.clientX + window.scrollX;
196 var delta = element.dragLastX - x;
197 var newWidth = constrainedWidthFromElement(leftPane.clientWidth - delta, main);
198 if ((leftPane.clientWidth - delta) == newWidth) // the width wasn't constrained
199 element.dragLastX = x;
200 leftPane.style.width = newWidth + "px";
201 rightPane.style.left = newWidth + "px";
202 event.preventDefault();
206 function dividerDrag(event) {
207 var element = document.getElementById("divider");
208 if (document.getElementById("divider").dragging == true) {
209 var main = document.getElementById("main");
210 var top = document.getElementById("info");
211 var bottom = document.getElementById("body");
212 var y = event.clientY + window.scrollY;
213 var delta = element.dragLastY - y;
214 var newHeight = constrainedHeightFromElement(top.clientHeight - delta, main);
215 if ((top.clientHeight - delta) == newHeight) // the height wasn't constrained
216 element.dragLastY = y;
217 top.style.height = newHeight + "px";
218 bottom.style.top = newHeight + "px";
219 event.preventDefault();
224 document.getElementById("divider").addEventListener("mousedown", sourceDividerDragStart, false);
225 document.getElementById("infoDivider").addEventListener("mousedown", infoDividerDragStart, false);
226 document.getElementById("variableColumnResizer").addEventListener("mousedown", columnResizerDragStart, false);
229 function isPaused() {
230 return DebuggerDocument.isPaused();
234 DebuggerDocument.pause();
240 removeStyleClass(currentRow, "current");
244 var stackframeTable = document.getElementById("stackframeTable");
245 stackframeTable.innerHTML = ""; // clear the content
246 var variablesTable = document.getElementById("variablesTable");
247 variablesTable.innerHTML = ""; // clear the content
249 currentCallFrame = null;
251 pauseOnNextStatement = false;
253 steppingOver = false;
256 DebuggerDocument.resume();
261 pauseOnNextStatement = false;
263 steppingOver = false;
265 DebuggerDocument.stepInto();
270 pauseOnNextStatement = false;
273 DebuggerDocument.resume();
278 pauseOnNextStatement = false;
279 steppingOver = false;
282 DebuggerDocument.resume();
285 function hasStyleClass(element, className)
287 return ( element.className.indexOf(className) != -1 );
290 function addStyleClass(element, className)
292 if (!hasStyleClass(element, className))
293 element.className += (element.className.length ? " " + className : className);
296 function removeStyleClass(element, className)
298 if (hasStyleClass(element, className))
299 element.className = element.className.replace(className, "");
302 function addBreakPoint(event)
304 var row = event.target.parentNode;
305 if (hasStyleClass(row, "breakpoint")) {
306 if (hasStyleClass(row, "disabled")) {
307 removeStyleClass(row, "disabled");
308 files[currentFile].breakpoints[parseInt(event.target.title)] = 1;
310 addStyleClass(row, "disabled");
311 files[currentFile].breakpoints[parseInt(event.target.title)] = -1;
314 addStyleClass(row, "breakpoint");
315 removeStyleClass(row, "disabled");
316 files[currentFile].breakpoints[parseInt(event.target.title)] = 1;
320 function moveBreakPoint(event)
322 if (hasStyleClass(event.target.parentNode, "breakpoint")) {
323 files[currentFile].breakpoints[parseInt(event.target.title)] = 0;
324 draggingBreakpoint = event.target;
325 draggingBreakpoint.started = false;
326 draggingBreakpoint.dragLastY = event.clientY + window.scrollY;
327 draggingBreakpoint.dragLastX = event.clientX + window.scrollX;
328 var sourcesDocument = document.getElementById("sources").contentDocument;
329 sourcesDocument.addEventListener("mousemove", breakpointDrag, true);
330 sourcesDocument.addEventListener("mouseup", breakpointDragEnd, true);
331 sourcesDocument.body.style.cursor = "default";
335 function breakpointDrag(event)
337 var sourcesDocument = document.getElementById("sources").contentDocument;
338 if (!draggingBreakpoint) {
339 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
340 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
341 sourcesDocument.body.style.cursor = null;
345 var x = event.clientX + window.scrollX;
346 var y = event.clientY + window.scrollY;
347 var deltaX = draggingBreakpoint.dragLastX - x;
348 var deltaY = draggingBreakpoint.dragLastY - y;
349 if (draggingBreakpoint.started || deltaX > 4 || deltaY > 4 || deltaX < -4 || deltaY < -4) {
350 if (!draggingBreakpoint.started) {
351 draggingBreakpoint.isDisabled = hasStyleClass(draggingBreakpoint.parentNode, "disabled");
352 removeStyleClass(draggingBreakpoint.parentNode, "breakpoint");
353 removeStyleClass(draggingBreakpoint.parentNode, "disabled");
354 draggingBreakpoint.started = true;
356 var dragImage = sourcesDocument.createElement("img");
357 if (draggingBreakpoint.isDisabled)
358 dragImage.src = "breakPointDisabled.tif";
360 dragImage.src = "breakPoint.tif";
361 dragImage.id = "breakpointDrag";
362 dragImage.style.top = y - 8 + "px";
363 dragImage.style.left = x - 12 + "px";
364 sourcesDocument.body.appendChild(dragImage);
366 var dragImage = sourcesDocument.getElementById("breakpointDrag");
368 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
369 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
370 sourcesDocument.body.style.cursor = null;
374 dragImage.style.top = y - 8 + "px";
375 dragImage.style.left = x - 12 + "px";
377 dragImage.style.visibility = "hidden";
379 dragImage.style.visibility = null;
382 draggingBreakpoint.dragLastX = x;
383 draggingBreakpoint.dragLastY = y;
387 function breakpointDragEnd(event)
389 var sourcesDocument = document.getElementById("sources").contentDocument;
390 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
391 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
392 sourcesDocument.body.style.cursor = null;
394 var dragImage = sourcesDocument.getElementById("breakpointDrag");
398 dragImage.parentNode.removeChild(dragImage);
400 var x = event.clientX + window.scrollX;
401 if (x > 40 || !draggingBreakpoint)
404 var y = event.clientY + window.scrollY;
405 var rowHeight = draggingBreakpoint.parentNode.offsetHeight;
406 var row = Math.ceil(y / rowHeight);
410 var file = files[currentFile];
411 var table = file.element.firstChild;
412 if (row > table.childNodes.length)
415 var tr = table.childNodes.item(row - 1);
419 if (draggingBreakpoint.isDisabled)
420 addStyleClass(tr, "disabled");
421 addStyleClass(tr, "breakpoint");
422 file.breakpoints[row] = (draggingBreakpoint.isDisabled ? -1 : 1);
424 draggingBreakpoint = null;
427 function totalOffsetTop(element, stop)
430 while (element.offsetParent) {
431 currentTop += element.offsetTop
432 element = element.offsetParent;
439 function switchFile()
441 var filesSelect = document.getElementById("files");
442 loadFile(filesSelect.options[filesSelect.selectedIndex].value, true);
445 function syntaxHighlight(code)
447 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 };
449 function echoChar(c) {
462 function isDigit(number) {
463 var string = "1234567890";
464 if (string.indexOf(number) != -1)
469 function isHex(hex) {
470 var string = "1234567890abcdefABCDEF";
471 if (string.indexOf(hex) != -1)
476 function isLetter(letter) {
477 var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
478 if (string.indexOf(letter) != -1)
487 for (var i = 0; i < code.length; i++) {
490 cNext = code.charAt(i + 1);
492 if (c == "/" && cNext == "*") {
493 result += "<span class=\"comment\">";
496 for (i += 2; i < code.length; i++) {
502 result += "<span class=\"comment\">";
503 if (cPrev == "*" && c == "/")
509 } else if (c == "/" && cNext == "/") {
510 result += "<span class=\"comment\">";
513 for (i += 2; i < code.length; i++) {
522 } else if (c == "\"" || c == "'") {
523 var instringtype = c;
525 result += "<span class=\"string\">";
527 for (i += 1; i < code.length; i++) {
529 if (stringstart < (i - 1) && cPrev == instringtype && code.charAt(i - 2) != "\\")
537 } else if (c == "0" && cNext == "x" && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
538 result += "<span class=\"number\">";
541 for (i += 2; i < code.length; i++) {
550 } else if ((isDigit(c) || ((c == "-" || c == ".") && isDigit(cNext))) && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
551 result += "<span class=\"number\">";
553 for (i += 1; i < code.length; i++) {
555 if (!isDigit(c) && c != ".")
562 } else if(isLetter(c) && (i == 0 || !isLetter(cPrev))) {
565 for (var j = i + 1; j < i + 12 && j < code.length; j++) {
572 if (keywords[keyword]) {
573 result += "<span class=\"keyword\">" + keyword + "</span>";
574 i += keyword.length - 1;
585 function navFilePrevious(element)
587 if (element.disabled)
589 var lastFile = previousFiles.pop();
590 if (currentFile != -1)
591 nextFiles.unshift(currentFile);
592 loadFile(lastFile, false);
595 function navFileNext(element)
597 if (element.disabled)
599 var lastFile = nextFiles.shift();
600 if (currentFile != -1)
601 previousFiles.push(currentFile);
602 loadFile(lastFile, false);
605 function updateFunctionStack()
607 var stackframeTable = document.getElementById("stackframeTable");
608 stackframeTable.innerHTML = ""; // clear the content
610 currentStack = new Array();
611 var stack = DebuggerDocument.currentFunctionStack();
612 for(var i = 0; i < stack.length; i++) {
613 var tr = document.createElement("tr");
614 var td = document.createElement("td");
615 td.className = "stackNumber";
619 td = document.createElement("td");
620 td.innerText = stack[i];
622 tr.addEventListener("click", selectStackFrame, true);
624 stackframeTable.appendChild(tr);
626 var frame = new ScriptCallFrame(stack[i], i, tr);
627 tr.callFrame = frame;
628 currentStack.push(frame);
631 addStyleClass(tr, "current");
632 frame.loadVariables();
633 currentCallFrame = frame;
638 function selectStackFrame(event)
640 var stackframeTable = document.getElementById("stackframeTable");
641 var rows = stackframeTable.childNodes;
642 for (var i = 0; i < rows.length; i++)
643 removeStyleClass(rows[i], "current");
644 addStyleClass(this, "current");
645 this.callFrame.loadVariables();
646 currentCallFrame = this.callFrame;
649 function selectVariable(event)
651 var variablesTable = document.getElementById("variablesTable");
652 var rows = variablesTable.childNodes;
653 for (var i = 0; i < rows.length; i++)
654 removeStyleClass(rows[i], "current");
655 addStyleClass(this, "current");
658 function loadFile(fileIndex, manageNavLists)
660 var file = files[fileIndex];
664 if (currentFile != -1 && files[currentFile] && files[currentFile].element)
665 files[currentFile].element.style.display = "none";
668 file.source = file.source.replace(/\r\n|\r/, "\n"); // normalize line endings
670 var sourcesDocument = document.getElementById("sources").contentDocument;
671 var sourcesDiv = sourcesDocument.body;
672 var sourceDiv = sourcesDocument.createElement("div");
673 sourceDiv.id = "file" + fileIndex;
674 sourcesDiv.appendChild(sourceDiv);
675 file.element = sourceDiv;
677 var table = sourcesDocument.createElement("table");
678 sourceDiv.appendChild(table);
680 var lines = syntaxHighlight(file.source).split("\n");
681 for( var i = 0; i < lines.length; i++ ) {
682 var tr = sourcesDocument.createElement("tr");
683 var td = sourcesDocument.createElement("td");
684 td.className = "gutter";
686 td.addEventListener("click", addBreakPoint, true);
687 td.addEventListener("mousedown", moveBreakPoint, true);
690 td = sourcesDocument.createElement("td");
691 td.className = "source";
692 td.innerHTML = (lines[i].length ? lines[i] : " ");
694 table.appendChild(tr);
700 file.element.style.display = null;
702 document.getElementById("filesPopupButtonContent").innerText = (file.url ? file.url : "(unknown script)");
704 var filesSelect = document.getElementById("files");
705 for (var i = 0; i < filesSelect.childNodes.length; i++) {
706 if (filesSelect.childNodes[i].value == fileIndex) {
707 filesSelect.selectedIndex = i;
712 if (manageNavLists) {
713 nextFiles = new Array();
714 if (currentFile != -1)
715 previousFiles.push(currentFile);
718 document.getElementById("navFileLeftButton").disabled = (previousFiles.length == 0);
719 document.getElementById("navFileRightButton").disabled = (nextFiles.length == 0);
721 currentFile = fileIndex;
724 function updateFileSource(source, url, force)
726 var fileIndex = filesLookup[url];
727 if (!fileIndex || !source.length)
730 var file = files[fileIndex];
731 if (force || file.source.length != source.length || file.source != source) {
732 file.source = source;
736 file.element.parentNode.removeChild(file.element);
740 if (currentFile == fileIndex)
741 loadFile(fileIndex, false);
745 function didParseScript(source, fileSource, url, sourceId, baseLineNumber)
747 var fileIndex = filesLookup[url];
748 var file = files[fileIndex];
749 var firstLoad = false;
750 if (!fileIndex || !file) {
751 fileIndex = files.length + 1;
753 filesLookup[url] = fileIndex;
756 file.scripts = new Array();
757 file.breakpoints = new Array();
758 file.source = (fileSource.length ? fileSource : source);
759 file.url = (url.length ? url : null);
762 files[fileIndex] = file;
764 var filesSelect = document.getElementById("files");
765 var option = document.createElement("option");
766 files[fileIndex].menuOption = option;
767 option.value = fileIndex;
768 option.text = (file.url ? file.url : "(unknown script)");
769 filesSelect.appendChild(option);
773 var sourceObj = new Object();
774 sourceObj.file = fileIndex;
775 sourceObj.baseLineNumber = baseLineNumber;
776 file.scripts.push(sourceId);
777 scripts[sourceId] = sourceObj;
780 updateFileSource((fileSource.length ? fileSource : source), url, false);
782 if (currentFile == -1)
783 loadFile(fileIndex, true);
786 function willExecuteStatement(sourceId, line)
788 var script = scripts[sourceId];
789 if (line <= 0 || !script)
792 var file = files[script.file];
796 if (pauseOnNextStatement || file.breakpoints[line] == 1 || (steppingOver && !steppingStack)) {
798 pauseOnNextStatement = false;
802 if (currentFile != script.file)
803 loadFile(script.file, true);
805 removeStyleClass(currentRow, "current");
808 if (line > file.element.firstChild.childNodes.length)
811 updateFunctionStack();
813 currentRow = file.element.firstChild.childNodes.item(line - 1);
817 addStyleClass(currentRow, "current");
819 var sourcesDiv = document.getElementById("sources");
820 var sourcesDocument = document.getElementById("sources").contentDocument;
821 var parent = sourcesDocument.body;
822 var offset = totalOffsetTop(currentRow, parent);
823 if (offset < (parent.scrollTop + 20) || offset > (parent.scrollTop + sourcesDiv.clientHeight - 20))
824 parent.scrollTop = totalOffsetTop(currentRow, parent) - (sourcesDiv.clientHeight / 2) + 10;
828 function didEnterCallFrame(sourceId, line)
830 if (steppingOver || steppingOut)
832 willExecuteStatement(sourceId, line);
835 function willLeaveCallFrame(sourceId, line)
839 willExecuteStatement(sourceId, line);
841 steppingOver = false;
842 if (steppingOut && !steppingStack) {
844 pauseOnNextStatement = true;
846 if ((steppingOver || steppingOut) && steppingStack >= 1)
850 function showConsoleWindow()
853 consoleWindow = window.open("console.html", "console", "top=200, left=200, width=500, height=300, toolbar=yes, resizable=yes");