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 previousFiles = new Array();
36 var nextFiles = new Array();
37 var isResizingColumn = false;
39 function sleep(numberMillis) {
41 var exitTime = now.getTime() + numberMillis;
44 if (now.getTime() > exitTime)
49 function columnResizerMouseOver(element) {
50 element.style.cursor = "move";
53 function columnResizerMouseOut(element) {
55 element.style.cursor = "arrow";
58 function headerMouseDown(element) {
59 if (!isResizingColumn)
60 element.style.background = "url(glossyHeaderPressed.png) repeat-x";
63 function headerMouseUp(element) {
64 element.style.background = "url(glossyHeader.png) repeat-x";
67 function headerMouseOut(element) {
68 element.style.background = "url(glossyHeader.png) repeat-x";
71 function dividerDragStart(element, dividerDrag, dividerDragEnd, event) {
72 element.dragging = true;
73 element.dragLastY = event.clientY + window.scrollY;
74 element.dragLastX = event.clientX + window.scrollX;
75 document.addEventListener("mousemove", dividerDrag, true);
76 document.addEventListener("mouseup", dividerDragEnd, true);
77 event.preventDefault();
80 function sourceDividerDragStart(event) {
81 dividerDragStart(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event);
84 function infoDividerDragStart(event) {
85 dividerDragStart(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event);
88 function columnResizerDragStart(event) {
89 isResizingColumn = true;
90 dividerDragStart(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
93 function columnResizerDragEnd(event) {
94 isResizingColumn = false;
95 dividerDragEnd(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
98 function infoDividerDragEnd(event) {
99 dividerDragEnd(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd);
102 function sourceDividerDragEnd(event) {
103 dividerDragEnd(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd);
106 function dividerDragEnd(element, dividerDrag, dividerDragEnd, event) {
107 element.dragging = false;
108 document.removeEventListener("mousemove", dividerDrag, true);
109 document.removeEventListener("mouseup", dividerDragEnd, true);
112 function columnResizerDrag(event) {
113 var element = document.getElementById("variableColumnResizer");
114 if (element.dragging == true) {
115 var main = document.getElementById("rightPane");
116 var variableColumn = document.getElementById("variable");
117 var x = event.clientX + window.scrollX;
118 var delta = element.dragLastX - x;
119 var newWidth = constrainedWidthFromElement(variableColumn.clientWidth - delta, main);
120 variableColumn.style.width = newWidth + "px";
121 element.style.left = newWidth + "px";
122 element.dragLastX = x;
123 event.preventDefault();
127 function constrainedWidthFromElement(width, element) {
128 if (width < element.clientWidth * 0.25)
129 width = element.clientWidth * 0.25;
130 else if (width > element.clientWidth * 0.75)
131 width = element.clientWidth * 0.75;
136 function constrainedHeightFromElement(height, element) {
137 if (height < element.clientHeight * 0.25)
138 height = element.clientHeight * 0.25;
139 else if (height > element.clientHeight * 0.75)
140 height = element.clientHeight * 0.75;
145 function infoDividerDrag(event) {
146 var element = document.getElementById("infoDivider");
147 if (document.getElementById("infoDivider").dragging == true) {
148 var main = document.getElementById("main");
149 var leftPane = document.getElementById("leftPane");
150 var rightPane = document.getElementById("rightPane");
151 var x = event.clientX + window.scrollX;
152 var delta = element.dragLastX - x;
153 var newWidth = constrainedWidthFromElement(leftPane.clientWidth - delta, main);
154 leftPane.style.width = newWidth + "px";
155 rightPane.style.left = newWidth + "px";
156 element.dragLastX = x;
157 event.preventDefault();
161 function dividerDrag(event) {
162 var element = document.getElementById("divider");
163 if (document.getElementById("divider").dragging == true) {
164 var main = document.getElementById("main");
165 var top = document.getElementById("info");
166 var bottom = document.getElementById("body");
167 var y = event.clientY + window.scrollY;
168 var delta = element.dragLastY - y;
169 var newHeight = constrainedHeightFromElement(top.clientHeight - delta, main);
170 top.style.height = newHeight + "px";
171 bottom.style.top = newHeight + "px";
172 element.dragLastY = y;
173 event.preventDefault();
178 document.getElementById("divider").addEventListener("mousedown", sourceDividerDragStart, false);
179 document.getElementById("infoDivider").addEventListener("mousedown", infoDividerDragStart, false);
180 document.getElementById("variableColumnResizer").addEventListener("mousedown", columnResizerDragStart, false);
183 function isPaused() {
184 return DebuggerDocument.isPaused();
188 DebuggerDocument.pause();
194 removeStyleClass(currentRow, "current");
199 var stackframeTable = document.getElementById("stackframeTable");
200 stackframeTable.innerHTML = ""; // clear the content
204 DebuggerDocument.resume();
209 DebuggerDocument.stepInto();
212 function hasStyleClass(element, className)
214 return ( element.className.indexOf(className) != -1 );
217 function addStyleClass(element, className)
219 if (!hasStyleClass(element, className))
220 element.className += (element.className.length ? " " + className : className);
223 function removeStyleClass(element, className)
225 if (hasStyleClass(element, className))
226 element.className = element.className.replace(className, "");
229 function addBreakPoint(event)
231 var row = event.target.parentNode;
232 if (hasStyleClass(row, "breakpoint")) {
233 if (hasStyleClass(row, "disabled")) {
234 removeStyleClass(row, "disabled");
235 files[currentFile].breakpoints[parseInt(event.target.title)] = 1;
237 addStyleClass(row, "disabled");
238 files[currentFile].breakpoints[parseInt(event.target.title)] = -1;
241 addStyleClass(row, "breakpoint");
242 removeStyleClass(row, "disabled");
243 files[currentFile].breakpoints[parseInt(event.target.title)] = 1;
247 function totalOffsetTop(element, stop)
250 if (element.offsetParent) {
251 while (element.offsetParent) {
252 currentTop += element.offsetTop
253 element = element.offsetParent;
261 function switchFile()
263 var filesSelect = document.getElementById("files");
264 loadFile(filesSelect.options[filesSelect.selectedIndex].value, true);
267 function syntaxHighlight(code)
269 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 };
271 function echoChar(c) {
284 function isDigit(number) {
285 var string = "1234567890";
286 if (string.indexOf(number) != -1)
291 function isHex(hex) {
292 var string = "1234567890abcdefABCDEF";
293 if (string.indexOf(hex) != -1)
298 function isLetter(letter) {
299 var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
300 if (string.indexOf(letter) != -1)
309 for (var i = 0; i < code.length; i++) {
312 cNext = code.charAt(i + 1);
314 if (c == "/" && cNext == "*") {
315 result += "<span class=\"comment\">";
318 for (i += 2; i < code.length; i++) {
320 if (c == "\n" || c == "\r")
323 if (c == "\n" || c == "\r")
324 result += "<span class=\"comment\">";
325 if (cPrev == "*" && c == "/")
331 } else if (c == "/" && cNext == "/") {
332 result += "<span class=\"comment\">";
335 for (i += 2; i < code.length; i++) {
337 if (c == "\n" || c == "\r")
344 } else if (c == "\"" || c == "'") {
345 var instringtype = c;
347 result += "<span class=\"string\">";
349 for (i += 1; i < code.length; i++) {
351 if (stringstart < (i - 1) && cPrev == instringtype && code.charAt(i - 2) != "\\")
359 } else if (c == "0" && cNext == "x" && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
360 result += "<span class=\"number\">";
363 for (i += 2; i < code.length; i++) {
372 } else if ((isDigit(c) || ((c == "-" || c == ".") && isDigit(cNext))) && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
373 result += "<span class=\"number\">";
375 for (i += 1; i < code.length; i++) {
377 if (!isDigit(c) && c != ".")
384 } else if(isLetter(c) && (i == 0 || !isLetter(cPrev))) {
387 for (var j = i + 1; j < i + 12 && j < code.length; j++) {
394 if (keywords[keyword]) {
395 result += "<span class=\"keyword\">" + keyword + "</span>";
396 i += keyword.length - 1;
407 function navFilePrevious(element)
409 if (element.disabled)
411 var lastFile = previousFiles.pop();
412 if (currentFile != -1)
413 nextFiles.unshift(currentFile);
414 loadFile(lastFile, false);
417 function navFileNext(element)
419 if (element.disabled)
421 var lastFile = nextFiles.shift();
422 if (currentFile != -1)
423 previousFiles.push(currentFile);
424 loadFile(lastFile, false);
427 function updateFunctionStack()
429 var stackframeTable = document.getElementById("stackframeTable");
430 stackframeTable.innerHTML = ""; // clear the content
432 currentStack = DebuggerDocument.currentFunctionStack();
433 for(var i = 0; i < currentStack.length; i++) {
434 var tr = document.createElement("tr");
435 var td = document.createElement("td");
436 td.className = "stackNumber";
440 td = document.createElement("td");
441 td.innerText = currentStack[i];
444 stackframeTable.appendChild(tr);
447 var tr = document.createElement("tr");
448 var td = document.createElement("td");
449 td.className = "stackNumber";
453 td = document.createElement("td");
454 td.innerText = "Global";
457 stackframeTable.appendChild(tr);
460 function loadFile(fileIndex, manageNavLists)
462 var file = files[fileIndex];
466 if (currentFile != -1 && files[currentFile] && files[currentFile].element)
467 files[currentFile].element.style.display = "none";
470 file.lines = file.source.split(/[\n\r]/);
472 var sourcesDocument = document.getElementById("sources").contentDocument;
473 var sourcesDiv = sourcesDocument.body;
474 var sourceDiv = sourcesDocument.createElement("div");
475 sourceDiv.id = "file" + fileIndex;
476 sourcesDiv.appendChild(sourceDiv);
477 file.element = sourceDiv;
479 var table = sourcesDocument.createElement("table");
480 sourceDiv.appendChild(table);
482 var lines = syntaxHighlight(file.source).split(/[\n\r]/);
483 for( var i = 0; i < lines.length; i++ ) {
484 var tr = sourcesDocument.createElement("tr");
485 var td = sourcesDocument.createElement("td");
486 td.className = "gutter";
488 td.addEventListener("click", addBreakPoint, true);
491 td = sourcesDocument.createElement("td");
492 td.className = "source";
493 td.innerHTML = lines[i];
495 table.appendChild(tr);
501 file.element.style.display = null;
503 document.getElementById("filesPopupButtonContent").innerText = (file.url ? file.url : "(unknown script)");
505 var filesSelect = document.getElementById("files");
506 for (var i = 0; i < filesSelect.childNodes.length; i++) {
507 if (filesSelect.childNodes[i].value == fileIndex) {
508 filesSelect.selectedIndex = i;
513 if (manageNavLists) {
514 nextFiles = new Array();
515 if (currentFile != -1)
516 previousFiles.push(currentFile);
519 document.getElementById("navFileLeftButton").disabled = (previousFiles.length == 0);
520 document.getElementById("navFileRightButton").disabled = (nextFiles.length == 0);
522 currentFile = fileIndex;
525 function updateFileSource(source, url, force)
527 var fileIndex = filesLookup[url];
528 if (!fileIndex || !source.length)
531 var file = files[fileIndex];
532 if (force || file.source.length != source.length || file.source != source) {
533 file.source = source;
538 file.element.parentNode.removeChild(file.element);
542 if (currentFile == fileIndex)
543 loadFile(fileIndex, false);
547 function didParseScript(source, fileSource, url, sourceId, baseLineNumber)
549 var fileIndex = filesLookup[url];
550 var file = files[fileIndex];
551 var firstLoad = false;
552 if (!fileIndex || !file) {
553 fileIndex = files.length + 1;
555 filesLookup[url] = fileIndex;
558 file.scripts = new Array();
559 file.breakpoints = new Array();
560 file.source = (fileSource.length ? fileSource : source);
561 file.url = (url.length ? url : null);
564 files[fileIndex] = file;
566 var filesSelect = document.getElementById("files");
567 var option = document.createElement("option");
568 files[fileIndex].menuOption = option;
569 option.value = fileIndex;
570 option.text = (file.url ? file.url : "(unknown script)");
571 filesSelect.appendChild(option);
575 var sourceObj = new Object();
576 sourceObj.source = source;
577 sourceObj.file = fileIndex;
578 sourceObj.baseLineNumber = baseLineNumber;
579 file.scripts.push(sourceId);
580 scripts[sourceId] = sourceObj;
583 updateFileSource((fileSource.length ? fileSource : source), url, false);
585 if (currentFile == -1)
586 loadFile(fileIndex, true);
589 function willExecuteStatement(sourceId, line)
591 var script = scripts[sourceId];
592 if (line <= 0 || !script)
595 var file = files[script.file];
599 if (file.breakpoints[line] == 1)
603 if (currentFile != script.file)
604 loadFile(script.file, true);
606 removeStyleClass(currentRow, "current");
609 if (file.element.firstChild.childNodes.length < line)
612 updateFunctionStack();
614 currentRow = file.element.firstChild.childNodes.item(line - 1);
615 addStyleClass(currentRow, "current");
617 var sourcesDiv = document.getElementById("sources");
618 var sourcesDocument = document.getElementById("sources").contentDocument;
619 var parent = sourcesDocument.body;
620 var offset = totalOffsetTop(currentRow, parent);
621 if (offset < (parent.scrollTop + 20) || offset > (parent.scrollTop + sourcesDiv.clientHeight - 20))
622 parent.scrollTop = totalOffsetTop(currentRow, parent) - (sourcesDiv.clientHeight / 2) + 10;
626 function didEnterCallFrame(sourceId, line)
628 willExecuteStatement(sourceId, line);
631 function willLeaveCallFrame(sourceId, line)
633 willExecuteStatement(sourceId, line);