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;
45 ScriptCallFrame = function (functionName, index, row)
47 this.functionName = functionName;
50 this.localVariableNames = null;
53 ScriptCallFrame.prototype.valueForScopeVariable = function (name)
55 return DebuggerDocument.valueForScopeVariableNamed_inCallFrame_(name, this.index);
58 ScriptCallFrame.prototype.loadVariables = function ()
60 if (!this.localVariableNames)
61 this.localVariableNames = DebuggerDocument.localScopeVariableNamesForCallFrame_(this.index);
63 var variablesTable = document.getElementById("variablesTable");
64 variablesTable.innerHTML = "";
66 for(var i = 0; i < this.localVariableNames.length; i++) {
67 var tr = document.createElement("tr");
68 var td = document.createElement("td");
69 td.innerText = this.localVariableNames[i];
70 td.className = "variable";
73 td = document.createElement("td");
74 td.innerText = this.valueForScopeVariable(this.localVariableNames[i]);
76 tr.addEventListener("click", selectVariable, true);
78 variablesTable.appendChild(tr);
82 function sleep(numberMillis) {
84 var exitTime = now.getTime() + numberMillis;
87 if (now.getTime() > exitTime)
92 function headerMouseDown(element) {
93 if (!isResizingColumn)
94 element.style.background = "url(glossyHeaderPressed.png) repeat-x";
97 function headerMouseUp(element) {
98 element.style.background = "url(glossyHeader.png) repeat-x";
101 function headerMouseOut(element) {
102 element.style.background = "url(glossyHeader.png) repeat-x";
105 function dividerDragStart(element, dividerDrag, dividerDragEnd, event, cursor) {
106 element.dragging = true;
107 element.dragLastY = event.clientY + window.scrollY;
108 element.dragLastX = event.clientX + window.scrollX;
109 document.addEventListener("mousemove", dividerDrag, true);
110 document.addEventListener("mouseup", dividerDragEnd, true);
111 document.body.style.cursor = cursor;
112 event.preventDefault();
115 function sourceDividerDragStart(event) {
116 dividerDragStart(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event, "row-resize");
119 function infoDividerDragStart(event) {
120 dividerDragStart(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event, "col-resize");
123 function columnResizerDragStart(event) {
124 isResizingColumn = true;
125 dividerDragStart(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event, "col-resize");
128 function columnResizerDragEnd(event) {
129 isResizingColumn = false;
130 dividerDragEnd(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
133 function infoDividerDragEnd(event) {
134 dividerDragEnd(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event);
137 function sourceDividerDragEnd(event) {
138 dividerDragEnd(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event);
141 function dividerDragEnd(element, dividerDrag, dividerDragEnd, event) {
142 element.dragging = false;
143 document.removeEventListener("mousemove", dividerDrag, true);
144 document.removeEventListener("mouseup", dividerDragEnd, true);
145 document.body.style.cursor = null;
148 function columnResizerDrag(event) {
149 var element = document.getElementById("variableColumnResizer");
150 if (element.dragging == true) {
151 var main = document.getElementById("rightPane");
152 var variableColumn = document.getElementById("variable");
153 var rules = document.defaultView.getMatchedCSSRules(variableColumn, "");
154 for (var i = 0; i < rules.length; i++) {
155 if (rules[i].selectorText == ".variable") {
156 var columnRule = rules[i];
161 var x = event.clientX + window.scrollX;
162 var delta = element.dragLastX - x;
163 var newWidth = constrainedWidthFromElement(variableColumn.clientWidth - delta, main);
164 if ((variableColumn.clientWidth - delta) == newWidth) // the width wasn't constrained
165 element.dragLastX = x;
166 columnRule.style.width = newWidth + "px";
167 element.style.left = newWidth + "px";
168 event.preventDefault();
172 function constrainedWidthFromElement(width, element) {
173 if (width < element.clientWidth * 0.25)
174 width = element.clientWidth * 0.25;
175 else if (width > element.clientWidth * 0.75)
176 width = element.clientWidth * 0.75;
180 function constrainedHeightFromElement(height, element) {
181 if (height < element.clientHeight * 0.25)
182 height = element.clientHeight * 0.25;
183 else if (height > element.clientHeight * 0.75)
184 height = element.clientHeight * 0.75;
188 function infoDividerDrag(event) {
189 var element = document.getElementById("infoDivider");
190 if (document.getElementById("infoDivider").dragging == true) {
191 var main = document.getElementById("main");
192 var leftPane = document.getElementById("leftPane");
193 var rightPane = document.getElementById("rightPane");
194 var x = event.clientX + window.scrollX;
195 var delta = element.dragLastX - x;
196 var newWidth = constrainedWidthFromElement(leftPane.clientWidth - delta, main);
197 if ((leftPane.clientWidth - delta) == newWidth) // the width wasn't constrained
198 element.dragLastX = x;
199 leftPane.style.width = newWidth + "px";
200 rightPane.style.left = newWidth + "px";
201 event.preventDefault();
205 function dividerDrag(event) {
206 var element = document.getElementById("divider");
207 if (document.getElementById("divider").dragging == true) {
208 var main = document.getElementById("main");
209 var top = document.getElementById("info");
210 var bottom = document.getElementById("body");
211 var y = event.clientY + window.scrollY;
212 var delta = element.dragLastY - y;
213 var newHeight = constrainedHeightFromElement(top.clientHeight - delta, main);
214 if ((top.clientHeight - delta) == newHeight) // the height wasn't constrained
215 element.dragLastY = y;
216 top.style.height = newHeight + "px";
217 bottom.style.top = newHeight + "px";
218 event.preventDefault();
223 document.getElementById("divider").addEventListener("mousedown", sourceDividerDragStart, false);
224 document.getElementById("infoDivider").addEventListener("mousedown", infoDividerDragStart, false);
225 document.getElementById("variableColumnResizer").addEventListener("mousedown", columnResizerDragStart, false);
228 function isPaused() {
229 return DebuggerDocument.isPaused();
233 DebuggerDocument.pause();
239 removeStyleClass(currentRow, "current");
243 var stackframeTable = document.getElementById("stackframeTable");
244 stackframeTable.innerHTML = ""; // clear the content
245 var variablesTable = document.getElementById("variablesTable");
246 variablesTable.innerHTML = ""; // clear the content
248 currentCallFrame = null;
250 pauseOnNextStatement = false;
252 steppingOver = false;
255 DebuggerDocument.resume();
260 pauseOnNextStatement = false;
262 steppingOver = false;
264 DebuggerDocument.stepInto();
269 pauseOnNextStatement = false;
272 DebuggerDocument.resume();
277 pauseOnNextStatement = false;
278 steppingOver = false;
281 DebuggerDocument.resume();
284 function hasStyleClass(element, className)
286 return ( element.className.indexOf(className) != -1 );
289 function addStyleClass(element, className)
291 if (!hasStyleClass(element, className))
292 element.className += (element.className.length ? " " + className : className);
295 function removeStyleClass(element, className)
297 if (hasStyleClass(element, className))
298 element.className = element.className.replace(className, "");
301 function addBreakPoint(event)
303 var row = event.target.parentNode;
304 if (hasStyleClass(row, "breakpoint")) {
305 if (hasStyleClass(row, "disabled")) {
306 removeStyleClass(row, "disabled");
307 files[currentFile].breakpoints[parseInt(event.target.title)] = 1;
309 addStyleClass(row, "disabled");
310 files[currentFile].breakpoints[parseInt(event.target.title)] = -1;
313 addStyleClass(row, "breakpoint");
314 removeStyleClass(row, "disabled");
315 files[currentFile].breakpoints[parseInt(event.target.title)] = 1;
319 function moveBreakPoint(event)
321 if (hasStyleClass(event.target.parentNode, "breakpoint")) {
322 files[currentFile].breakpoints[parseInt(event.target.title)] = 0;
323 draggingBreakpoint = event.target;
324 draggingBreakpoint.started = false;
325 draggingBreakpoint.dragLastY = event.clientY + window.scrollY;
326 draggingBreakpoint.dragLastX = event.clientX + window.scrollX;
327 var sourcesDocument = document.getElementById("sources").contentDocument;
328 sourcesDocument.addEventListener("mousemove", breakpointDrag, true);
329 sourcesDocument.addEventListener("mouseup", breakpointDragEnd, true);
330 sourcesDocument.body.style.cursor = "default";
334 function breakpointDrag(event)
336 var sourcesDocument = document.getElementById("sources").contentDocument;
337 if (!draggingBreakpoint) {
338 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
339 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
340 sourcesDocument.body.style.cursor = null;
344 var x = event.clientX + window.scrollX;
345 var y = event.clientY + window.scrollY;
346 var deltaX = draggingBreakpoint.dragLastX - x;
347 var deltaY = draggingBreakpoint.dragLastY - y;
348 if (draggingBreakpoint.started || deltaX > 4 || deltaY > 4 || deltaX < -4 || deltaY < -4) {
349 if (!draggingBreakpoint.started) {
350 draggingBreakpoint.isDisabled = hasStyleClass(draggingBreakpoint.parentNode, "disabled");
351 removeStyleClass(draggingBreakpoint.parentNode, "breakpoint");
352 removeStyleClass(draggingBreakpoint.parentNode, "disabled");
353 draggingBreakpoint.started = true;
355 var dragImage = sourcesDocument.createElement("img");
356 if (draggingBreakpoint.isDisabled)
357 dragImage.src = "breakPointDisabled.tif";
359 dragImage.src = "breakPoint.tif";
360 dragImage.id = "breakpointDrag";
361 dragImage.style.top = y - 8 + "px";
362 dragImage.style.left = x - 12 + "px";
363 sourcesDocument.body.appendChild(dragImage);
365 var dragImage = sourcesDocument.getElementById("breakpointDrag");
367 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
368 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
369 sourcesDocument.body.style.cursor = null;
373 dragImage.style.top = y - 8 + "px";
374 dragImage.style.left = x - 12 + "px";
376 dragImage.style.visibility = "hidden";
378 dragImage.style.visibility = null;
381 draggingBreakpoint.dragLastX = x;
382 draggingBreakpoint.dragLastY = y;
386 function breakpointDragEnd(event)
388 var sourcesDocument = document.getElementById("sources").contentDocument;
389 sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
390 sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
391 sourcesDocument.body.style.cursor = null;
393 var dragImage = sourcesDocument.getElementById("breakpointDrag");
397 dragImage.parentNode.removeChild(dragImage);
399 var x = event.clientX + window.scrollX;
400 if (x > 40 || !draggingBreakpoint)
403 var y = event.clientY + window.scrollY;
404 var rowHeight = draggingBreakpoint.parentNode.offsetHeight;
405 var row = Math.ceil(y / rowHeight);
409 var file = files[currentFile];
410 var table = file.element.firstChild;
411 if (row > table.childNodes.length)
414 var tr = table.childNodes.item(row - 1);
418 if (draggingBreakpoint.isDisabled)
419 addStyleClass(tr, "disabled");
420 addStyleClass(tr, "breakpoint");
421 file.breakpoints[row] = (draggingBreakpoint.isDisabled ? -1 : 1);
423 draggingBreakpoint = null;
426 function totalOffsetTop(element, stop)
429 while (element.offsetParent) {
430 currentTop += element.offsetTop
431 element = element.offsetParent;
438 function switchFile()
440 var filesSelect = document.getElementById("files");
441 loadFile(filesSelect.options[filesSelect.selectedIndex].value, true);
444 function syntaxHighlight(code)
446 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 };
448 function echoChar(c) {
461 function isDigit(number) {
462 var string = "1234567890";
463 if (string.indexOf(number) != -1)
468 function isHex(hex) {
469 var string = "1234567890abcdefABCDEF";
470 if (string.indexOf(hex) != -1)
475 function isLetter(letter) {
476 var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
477 if (string.indexOf(letter) != -1)
486 for (var i = 0; i < code.length; i++) {
489 cNext = code.charAt(i + 1);
491 if (c == "/" && cNext == "*") {
492 result += "<span class=\"comment\">";
495 for (i += 2; i < code.length; i++) {
501 result += "<span class=\"comment\">";
502 if (cPrev == "*" && c == "/")
508 } else if (c == "/" && cNext == "/") {
509 result += "<span class=\"comment\">";
512 for (i += 2; i < code.length; i++) {
521 } else if (c == "\"" || c == "'") {
522 var instringtype = c;
524 result += "<span class=\"string\">";
526 for (i += 1; i < code.length; i++) {
528 if (stringstart < (i - 1) && cPrev == instringtype && code.charAt(i - 2) != "\\")
536 } else if (c == "0" && cNext == "x" && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
537 result += "<span class=\"number\">";
540 for (i += 2; i < code.length; i++) {
549 } else if ((isDigit(c) || ((c == "-" || c == ".") && isDigit(cNext))) && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
550 result += "<span class=\"number\">";
552 for (i += 1; i < code.length; i++) {
554 if (!isDigit(c) && c != ".")
561 } else if(isLetter(c) && (i == 0 || !isLetter(cPrev))) {
564 for (var j = i + 1; j < i + 12 && j < code.length; j++) {
571 if (keywords[keyword]) {
572 result += "<span class=\"keyword\">" + keyword + "</span>";
573 i += keyword.length - 1;
584 function navFilePrevious(element)
586 if (element.disabled)
588 var lastFile = previousFiles.pop();
589 if (currentFile != -1)
590 nextFiles.unshift(currentFile);
591 loadFile(lastFile, false);
594 function navFileNext(element)
596 if (element.disabled)
598 var lastFile = nextFiles.shift();
599 if (currentFile != -1)
600 previousFiles.push(currentFile);
601 loadFile(lastFile, false);
604 function updateFunctionStack()
606 var stackframeTable = document.getElementById("stackframeTable");
607 stackframeTable.innerHTML = ""; // clear the content
609 currentStack = new Array();
610 var stack = DebuggerDocument.currentFunctionStack();
611 for(var i = 0; i < stack.length; i++) {
612 var tr = document.createElement("tr");
613 var td = document.createElement("td");
614 td.className = "stackNumber";
618 td = document.createElement("td");
619 td.innerText = stack[i];
621 tr.addEventListener("click", selectStackFrame, true);
623 stackframeTable.appendChild(tr);
625 var frame = new ScriptCallFrame(stack[i], i, tr);
626 tr.callFrame = frame;
627 currentStack.push(frame);
630 addStyleClass(tr, "current");
631 frame.loadVariables();
632 currentCallFrame = frame;
637 function selectStackFrame(event)
639 var stackframeTable = document.getElementById("stackframeTable");
640 var rows = stackframeTable.childNodes;
641 for (var i = 0; i < rows.length; i++)
642 removeStyleClass(rows[i], "current");
643 addStyleClass(this, "current");
644 this.callFrame.loadVariables();
645 currentCallFrame = this.callFrame;
648 function selectVariable(event)
650 var variablesTable = document.getElementById("variablesTable");
651 var rows = variablesTable.childNodes;
652 for (var i = 0; i < rows.length; i++)
653 removeStyleClass(rows[i], "current");
654 addStyleClass(this, "current");
657 function loadFile(fileIndex, manageNavLists)
659 var file = files[fileIndex];
663 if (currentFile != -1 && files[currentFile] && files[currentFile].element)
664 files[currentFile].element.style.display = "none";
667 file.source = file.source.replace(/\r\n|\r/, "\n"); // normalize line endings
669 var sourcesDocument = document.getElementById("sources").contentDocument;
670 var sourcesDiv = sourcesDocument.body;
671 var sourceDiv = sourcesDocument.createElement("div");
672 sourceDiv.id = "file" + fileIndex;
673 sourcesDiv.appendChild(sourceDiv);
674 file.element = sourceDiv;
676 var table = sourcesDocument.createElement("table");
677 sourceDiv.appendChild(table);
679 var lines = syntaxHighlight(file.source).split("\n");
680 for( var i = 0; i < lines.length; i++ ) {
681 var tr = sourcesDocument.createElement("tr");
682 var td = sourcesDocument.createElement("td");
683 td.className = "gutter";
685 td.addEventListener("click", addBreakPoint, true);
686 td.addEventListener("mousedown", moveBreakPoint, true);
689 td = sourcesDocument.createElement("td");
690 td.className = "source";
691 td.innerHTML = (lines[i].length ? lines[i] : " ");
693 table.appendChild(tr);
699 file.element.style.display = null;
701 document.getElementById("filesPopupButtonContent").innerText = (file.url ? file.url : "(unknown script)");
703 var filesSelect = document.getElementById("files");
704 for (var i = 0; i < filesSelect.childNodes.length; i++) {
705 if (filesSelect.childNodes[i].value == fileIndex) {
706 filesSelect.selectedIndex = i;
711 if (manageNavLists) {
712 nextFiles = new Array();
713 if (currentFile != -1)
714 previousFiles.push(currentFile);
717 document.getElementById("navFileLeftButton").disabled = (previousFiles.length == 0);
718 document.getElementById("navFileRightButton").disabled = (nextFiles.length == 0);
720 currentFile = fileIndex;
723 function updateFileSource(source, url, force)
725 var fileIndex = filesLookup[url];
726 if (!fileIndex || !source.length)
729 var file = files[fileIndex];
730 if (force || file.source.length != source.length || file.source != source) {
731 file.source = source;
735 file.element.parentNode.removeChild(file.element);
739 if (currentFile == fileIndex)
740 loadFile(fileIndex, false);
744 function didParseScript(source, fileSource, url, sourceId, baseLineNumber)
746 var fileIndex = filesLookup[url];
747 var file = files[fileIndex];
748 var firstLoad = false;
749 if (!fileIndex || !file) {
750 fileIndex = files.length + 1;
752 filesLookup[url] = fileIndex;
755 file.scripts = new Array();
756 file.breakpoints = new Array();
757 file.source = (fileSource.length ? fileSource : source);
758 file.url = (url.length ? url : null);
761 files[fileIndex] = file;
763 var filesSelect = document.getElementById("files");
764 var option = document.createElement("option");
765 files[fileIndex].menuOption = option;
766 option.value = fileIndex;
767 option.text = (file.url ? file.url : "(unknown script)");
768 filesSelect.appendChild(option);
772 var sourceObj = new Object();
773 sourceObj.file = fileIndex;
774 sourceObj.baseLineNumber = baseLineNumber;
775 file.scripts.push(sourceId);
776 scripts[sourceId] = sourceObj;
779 updateFileSource((fileSource.length ? fileSource : source), url, false);
781 if (currentFile == -1)
782 loadFile(fileIndex, true);
785 function willExecuteStatement(sourceId, line)
787 var script = scripts[sourceId];
788 if (line <= 0 || !script)
791 var file = files[script.file];
795 if (pauseOnNextStatement || file.breakpoints[line] == 1 || (steppingOver && !steppingStack)) {
797 pauseOnNextStatement = false;
801 if (currentFile != script.file)
802 loadFile(script.file, true);
804 removeStyleClass(currentRow, "current");
807 if (line > file.element.firstChild.childNodes.length)
810 updateFunctionStack();
812 currentRow = file.element.firstChild.childNodes.item(line - 1);
816 addStyleClass(currentRow, "current");
818 var sourcesDiv = document.getElementById("sources");
819 var sourcesDocument = document.getElementById("sources").contentDocument;
820 var parent = sourcesDocument.body;
821 var offset = totalOffsetTop(currentRow, parent);
822 if (offset < (parent.scrollTop + 20) || offset > (parent.scrollTop + sourcesDiv.clientHeight - 20))
823 parent.scrollTop = totalOffsetTop(currentRow, parent) - (sourcesDiv.clientHeight / 2) + 10;
827 function didEnterCallFrame(sourceId, line)
829 if (steppingOver || steppingOut)
831 willExecuteStatement(sourceId, line);
834 function willLeaveCallFrame(sourceId, line)
838 willExecuteStatement(sourceId, line);
840 steppingOver = false;
841 if (steppingOut && !steppingStack) {
843 pauseOnNextStatement = true;
845 if ((steppingOver || steppingOut) && steppingStack >= 1)