blob: 076fedf492917544b4321ccc326075be65f4f29f [file] [log] [blame]
/*
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
* Copyright (C) 2006 David Smith (catfish.man@gmail.com)
* Copyright (C) 2007 Vladimir Olexa (vladimir.olexa@gmail.com)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var files = new Array();
var filesLookup = new Object();
var scripts = new Array();
var currentFile = -1;
var currentRow = null;
var currentStack = null;
var currentCallFrame = null;
var lastStatement = null;
var frameLineNumberStack = new Array();
var previousFiles = new Array();
var nextFiles = new Array();
var isResizingColumn = false;
var draggingBreakpoint = null;
var steppingOut = false;
var steppingOver = false;
var steppingStack = 0;
var pauseOnNextStatement = false;
var pausedWhileLeavingFrame = false;
var consoleWindow = null;
var breakpointEditorHTML = DebuggerDocument.breakpointEditorHTML();
var pendingAction = null;
var isPaused = false;
ScriptCallFrame = function (functionName, index, row)
{
this.functionName = functionName;
this.index = index;
this.row = row;
this.localVariableNames = null;
}
ScriptCallFrame.prototype.valueForScopeVariable = function (name)
{
return DebuggerDocument.valueForScopeVariableNamed(name, this.index);
}
ScriptCallFrame.prototype.loadVariables = function ()
{
if (!this.localVariableNames)
this.localVariableNames = DebuggerDocument.localScopeVariableNamesForCallFrame(this.index);
var variablesTable = document.getElementById("variablesTable");
variablesTable.innerHTML = "";
if (!this.localVariableNames)
return;
for(var i = 0; i < this.localVariableNames.length; i++) {
var tr = document.createElement("tr");
var td = document.createElement("td");
td.innerText = this.localVariableNames[i];
td.className = "variable";
tr.appendChild(td);
td = document.createElement("td");
td.innerText = this.valueForScopeVariable(this.localVariableNames[i]);
tr.appendChild(td);
tr.addEventListener("click", selectVariable, true);
variablesTable.appendChild(tr);
}
}
function sleep(numberMillis)
{
var now = new Date();
var exitTime = now.getTime() + numberMillis;
while (true) {
now = new Date();
if (now.getTime() > exitTime)
return;
}
}
function headerMouseDown(element)
{
if (!isResizingColumn)
element.style.background = "url(glossyHeaderPressed.png) repeat-x";
}
function headerMouseUp(element)
{
element.style.background = "url(glossyHeader.png) repeat-x";
}
function headerMouseOut(element)
{
element.style.background = "url(glossyHeader.png) repeat-x";
}
function filesDividerDragStart(event)
{
dividerDragStart(document.getElementById("filesDivider"), filesDividerDrag, filesDividerDragEnd, event, "col-resize");
}
function filesDividerDragEnd(event)
{
dividerDragEnd(document.getElementById("filesDivider"), filesDividerDrag, filesDividerDragEnd, event);
}
function filesDividerDrag(event)
{
var element = document.getElementById("filesDivider");
if (document.getElementById("filesDivider").dragging == true) {
var masterMain = document.getElementById("masterMain");
var main = document.getElementById("main");
var fileBrowser = document.getElementById("fileBrowser");
var x = event.clientX + window.scrollX;
var delta = element.dragLastX - x;
var newWidth = constrainedWidthFromElement(fileBrowser.clientWidth - delta, masterMain, 0.1, 0.9);
if ((fileBrowser.clientWidth - delta) == newWidth) // the width wasn't constrained
element.dragLastX = x;
fileBrowser.style.width = newWidth + "px";
main.style.left = newWidth + "px";
event.preventDefault();
}
}
function dividerDragStart(element, dividerDrag, dividerDragEnd, event, cursor)
{
element.dragging = true;
element.dragLastY = event.clientY + window.scrollY;
element.dragLastX = event.clientX + window.scrollX;
document.addEventListener("mousemove", dividerDrag, true);
document.addEventListener("mouseup", dividerDragEnd, true);
document.body.style.cursor = cursor;
event.preventDefault();
}
function dividerDragEnd(element, dividerDrag, dividerDragEnd, event)
{
element.dragging = false;
document.removeEventListener("mousemove", dividerDrag, true);
document.removeEventListener("mouseup", dividerDragEnd, true);
document.body.style.removeProperty("cursor");
}
function dividerDrag(event)
{
var element = document.getElementById("divider");
if (document.getElementById("divider").dragging == true) {
var main = document.getElementById("main");
var top = document.getElementById("info");
var bottom = document.getElementById("body");
var y = event.clientY + window.scrollY;
var delta = element.dragLastY - y;
var newHeight = constrainedHeightFromElement(top.clientHeight - delta, main);
if ((top.clientHeight - delta) == newHeight) // the height wasn't constrained
element.dragLastY = y;
top.style.height = newHeight + "px";
bottom.style.top = newHeight + "px";
event.preventDefault();
}
}
function sourceDividerDragStart(event)
{
dividerDragStart(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event, "row-resize");
}
function sourceDividerDragEnd(event)
{
dividerDragEnd(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event);
}
function infoDividerDragStart(event)
{
dividerDragStart(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event, "col-resize");
}
function infoDividerDragEnd(event)
{
dividerDragEnd(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event);
}
function infoDividerDrag(event)
{
var element = document.getElementById("infoDivider");
if (document.getElementById("infoDivider").dragging == true) {
var main = document.getElementById("main");
var leftPane = document.getElementById("leftPane");
var rightPane = document.getElementById("rightPane");
var x = event.clientX + window.scrollX;
var delta = element.dragLastX - x;
var newWidth = constrainedWidthFromElement(leftPane.clientWidth - delta, main);
if ((leftPane.clientWidth - delta) == newWidth) // the width wasn't constrained
element.dragLastX = x;
leftPane.style.width = newWidth + "px";
rightPane.style.left = newWidth + "px";
event.preventDefault();
}
}
function columnResizerDragStart(event)
{
isResizingColumn = true;
dividerDragStart(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event, "col-resize");
}
function columnResizerDragEnd(event)
{
isResizingColumn = false;
dividerDragEnd(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
}
function columnResizerDrag(event)
{
var element = document.getElementById("variableColumnResizer");
if (element.dragging == true) {
var main = document.getElementById("rightPane");
var variableColumn = document.getElementById("variable");
var rules = document.defaultView.getMatchedCSSRules(variableColumn, "");
for (var i = 0; i < rules.length; i++) {
if (rules[i].selectorText == ".variable") {
var columnRule = rules[i];
break;
}
}
var x = event.clientX + window.scrollX;
var delta = element.dragLastX - x;
var newWidth = constrainedWidthFromElement(variableColumn.clientWidth - delta, main);
if ((variableColumn.clientWidth - delta) == newWidth) // the width wasn't constrained
element.dragLastX = x;
columnRule.style.width = newWidth + "px";
element.style.left = newWidth + "px";
event.preventDefault();
}
}
function constrainedWidthFromElement(width, element, constrainLeft, constrainRight)
{
if (constrainLeft === undefined) constrainLeft = 0.25;
if (constrainRight === undefined) constrainRight = 0.75;
if (width < element.clientWidth * constrainLeft)
width = element.clientWidth * constrainLeft;
else if (width > element.clientWidth * constrainRight)
width = element.clientWidth * constrainRight;
return width;
}
function constrainedHeightFromElement(height, element)
{
if (height < element.clientHeight * 0.25)
height = element.clientHeight * 0.25;
else if (height > element.clientHeight * 0.75)
height = element.clientHeight * 0.75;
return height;
}
function loaded()
{
document.getElementById("divider").addEventListener("mousedown", sourceDividerDragStart, false);
document.getElementById("infoDivider").addEventListener("mousedown", infoDividerDragStart, false);
document.getElementById("filesDivider").addEventListener("mousedown", filesDividerDragStart, false);
document.getElementById("variableColumnResizer").addEventListener("mousedown", columnResizerDragStart, false);
}
function pause()
{
DebuggerDocument.pause();
isPaused = true;
}
function resume()
{
if (currentRow) {
currentRow.removeStyleClass("current");
currentRow = null;
}
var stackframeTable = document.getElementById("stackframeTable");
stackframeTable.innerHTML = ""; // clear the content
var variablesTable = document.getElementById("variablesTable");
variablesTable.innerHTML = ""; // clear the content
currentStack = null;
currentCallFrame = null;
pauseOnNextStatement = false;
pausedWhileLeavingFrame = false;
steppingOut = false;
steppingOver = false;
steppingStack = 0;
DebuggerDocument.resume();
isPaused = false;
}
function stepInto()
{
pauseOnNextStatement = false;
steppingOut = false;
steppingOver = false;
steppingStack = 0;
DebuggerDocument.stepInto();
}
function stepOver()
{
pauseOnNextStatement = false;
steppingOver = true;
steppingStack = 0;
DebuggerDocument.resume();
}
function stepOut()
{
pauseOnNextStatement = pausedWhileLeavingFrame;
steppingOver = false;
steppingStack = 0;
steppingOut = true;
DebuggerDocument.resume();
}
Element.prototype.removeStyleClass = function(className)
{
if (this.hasStyleClass(className))
this.className = this.className.replace(className, "");
}
Element.prototype.addStyleClass = function(className)
{
if (!this.hasStyleClass(className))
this.className += (this.className.length ? " " + className : className);
}
Element.prototype.hasStyleClass = function(className)
{
return this.className.indexOf(className) != -1;
}
Element.prototype.firstParentWithClass = function(className)
{
var node = this.parentNode;
while(!node.hasStyleClass(className)) {
if (node == document)
return null;
node = node.parentNode;
}
return node;
}
Element.prototype.query = function(query)
{
return document.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}
Element.prototype.removeChildren = function()
{
while (this.firstChild)
this.removeChild(this.firstChild);
}
function breakpointAction(event)
{
var file = files[currentFile];
var lineNum = event.target.title;
if (!file.breakpoints[lineNum])
file.breakpoints[lineNum] = new BreakPoint(event.target.parentNode, file, lineNum);
else
toggleBreakpointOnLine(lineNum);
}
BreakPoint = function(row, file, line)
{
this.row = row;
this.file = file;
this.line = line;
row.addStyleClass("breakpoint");
row.removeStyleClass("disabled");
this.value = "break";
this.enabled = true;
this.editor = null;
this.type = 0;
this.hitcount = 0;
}
function toggleBreakpointEditorOnLine(lineNum)
{
if (pendingAction) {
clearTimeout(pendingAction);
pendingAction = null;
}
var file = files[currentFile];
bp = file.breakpoints[lineNum];
if (bp) {
var editor = bp.editor;
if (!editor) {
var sourcesDocument = document.getElementById("sources").contentDocument;
editor = sourcesDocument.createElement("div");
editor.className = "editor";
editor.id = lineNum;
editor.innerHTML = breakpointEditorHTML;
bp.row.childNodes[1].appendChild(editor);
bp.editor = editor;
file.breakpoints[lineNum] = bp;
editor.query('.//input[@class="enable"]').checked = bp.enabled;
editor.query('.//select[@class="editorDropdown"]').selectedIndex = bp.type;
updateBreakpointTypeOnLine(lineNum);
editor.query('.//span[@class="hitCounter"]').innerText = bp.hitcount;
setConditionFieldText(bp);
} else {
saveBreakpointOnLine(lineNum);
bp.row.childNodes[1].removeChild(editor);
bp.editor = null;
}
}
}
function updateBreakpointTypeOnLine(line)
{
var breakpoint = files[currentFile].breakpoints[line];
var editor = breakpoint.editor;
var label = editor.query('.//label[@class="conditionLabel"]');
var dropdown = editor.query('.//select[@class="editorDropdown"]');
breakpoint.type = dropdown.selectedIndex;
switch(breakpoint.type) {
case 0:
label.innerText = "Condition:";
break;
case 1:
label.innerText = "Expression:";
break;
}
}
function setConditionFieldText(breakpoint)
{
var conditionField = breakpoint.editor.query('.//div[@class="condition"]');
var functionBody = breakpoint.value;
if (!functionBody || functionBody == "break")
functionBody = "";
else {
var startIndex = functionBody.indexOf("return((") + 8;
var endIndex = null;
if (startIndex != 7) //-1 + 8, yes, that's lame
endIndex = functionBody.lastIndexOf("))");
else {
startIndex = functionBody.indexOf("{") + 1;
endIndex = functionBody.lastIndexOf("}");
}
functionBody = functionBody.substring(startIndex, endIndex);
}
conditionField.innerText = functionBody;
conditionField.addEventListener("keyup", new Function("saveBreakpointOnLine(" + breakpoint.line + ");"), false);
conditionField.focus();
}
function saveBreakpointOnLine(lineNum)
{
var file = files[currentFile];
var breakpoint = file.breakpoints[lineNum];
row = file.element.firstChild.childNodes.item(lineNum - 1);
var editor = breakpoint.editor;
var body = editor.query('.//div[@class="condition"]').innerText;
var actionIndex = editor.query('.//select[@class="editorDropdown"]').selectedIndex;
if (body.length == 0)
breakpoint.value = "break";
else if (body.indexOf("return") != -1)
breakpoint.value = "__drosera_breakpoint_conditional_func = function() {" + body + "}; __drosera_breakpoint_conditional_func();";
else
breakpoint.value = "__drosera_breakpoint_conditional_func = function() { return((" + body + ")); }; __drosera_breakpoint_conditional_func();";
}
function toggleBreakpointOnLine(lineNum)
{
var breakpoint = files[currentFile].breakpoints[lineNum];
pendingAction = null;
if (breakpoint.enabled)
breakpoint.row.addStyleClass("disabled");
else
breakpoint.row.removeStyleClass("disabled");
var hack = breakpoint.row.offsetTop; // force a relayout if needed.
breakpoint.enabled = !breakpoint.enabled;
var editor = breakpoint.editor;
if (editor) {
editor.query('.//input[@class="enable"]').checked = breakpoint.enabled;
setConditionFieldText(breakpoint, lineNum);
}
}
function moveBreakPoint(event)
{
if (event.target.parentNode.hasStyleClass("breakpoint")) {
draggingBreakpoint = event.target;
draggingBreakpoint.started = false;
draggingBreakpoint.dragLastY = event.clientY + window.scrollY;
draggingBreakpoint.dragLastX = event.clientX + window.scrollX;
var sourcesDocument = document.getElementById("sources").contentDocument;
sourcesDocument.addEventListener("mousemove", breakpointDrag, true);
sourcesDocument.addEventListener("mouseup", breakpointDragEnd, true);
sourcesDocument.body.style.cursor = "default";
}
}
function breakpointDrag(event)
{
var sourcesDocument = document.getElementById("sources").contentDocument;
if (!draggingBreakpoint) {
sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
sourcesDocument.body.style.removeProperty("cursor");
return;
}
var x = event.clientX + window.scrollX;
var y = event.clientY + window.scrollY;
var deltaX = draggingBreakpoint.dragLastX - x;
var deltaY = draggingBreakpoint.dragLastY - y;
if (draggingBreakpoint.started || deltaX > 4 || deltaY > 4 || deltaX < -4 || deltaY < -4) {
if (!draggingBreakpoint.started) {
var lineNum = draggingBreakpoint.title;
var file = files[currentFile];
var breakpoint = file.breakpoints[lineNum];
draggingBreakpoint.breakpoint = breakpoint;
breakpoint.row.removeStyleClass("breakpoint");
breakpoint.row.removeStyleClass("disabled");
var editor = breakpoint.editor;
if (editor)
toggleBreakpointEditorOnLine(lineNum);
draggingBreakpoint.started = true;
file.breakpoints[lineNum] = null;
var dragImage = sourcesDocument.createElement("img");
if (draggingBreakpoint.breakpoint.enabled)
dragImage.src = "breakPoint.tif";
else
dragImage.src = "breakPointDisabled.tif";
dragImage.id = "breakpointDrag";
dragImage.style.top = y - 8 + "px";
dragImage.style.left = x - 12 + "px";
sourcesDocument.body.appendChild(dragImage);
} else {
var dragImage = sourcesDocument.getElementById("breakpointDrag");
if (!dragImage) {
sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
sourcesDocument.body.style.removeProperty("cursor");
return;
}
dragImage.style.top = y - 8 + "px";
dragImage.style.left = x - 12 + "px";
if (x > 40)
dragImage.style.visibility = "hidden";
else
dragImage.style.removeProperty("visibility");
}
draggingBreakpoint.dragLastX = x;
draggingBreakpoint.dragLastY = y;
}
}
function breakpointDragEnd(event)
{
var sourcesDocument = document.getElementById("sources").contentDocument;
sourcesDocument.removeEventListener("mousemove", breakpointDrag, true);
sourcesDocument.removeEventListener("mouseup", breakpointDragEnd, true);
sourcesDocument.body.style.removeProperty("cursor");
var dragImage = sourcesDocument.getElementById("breakpointDrag");
if (!dragImage)
return;
dragImage.parentNode.removeChild(dragImage);
var x = event.clientX + window.scrollX;
if (x > 40 || !draggingBreakpoint)
return;
var y = event.clientY + window.scrollY;
var rowHeight = draggingBreakpoint.parentNode.offsetHeight;
var row = Math.ceil(y / rowHeight);
if (row <= 0)
row = 1;
var file = files[currentFile];
var table = file.element.firstChild;
if (row > table.childNodes.length)
return;
var tr = table.childNodes.item(row - 1);
if (!tr)
return;
var breakpoint = draggingBreakpoint.breakpoint;
breakpoint.row = tr;
// leave the editor there if it exists... we'll want to update it to the new values
breakpoint.editor = file.breakpoints[row].editor;
file.breakpoints[row] = breakpoint;
if (breakpoint.editor) {
breakpoint.editor.id = row;
updateBreakpointTypeOnLine(row);
setConditionFieldText(breakpoint);
}
if (!breakpoint.enabled)
tr.addStyleClass("disabled");
tr.addStyleClass("breakpoint");
draggingBreakpoint = null;
}
function totalOffsetTop(element, stop)
{
var currentTop = 0;
while (element.offsetParent) {
currentTop += element.offsetTop
element = element.offsetParent;
if (element == stop)
break;
}
return currentTop;
}
function switchFile(fileIndex)
{
var filesSelect = document.getElementById("files");
if (fileIndex === undefined)
fileIndex = filesSelect.selectedIndex;
fileClicked(filesSelect.options[fileIndex].value, false);
loadFile(filesSelect.options[fileIndex].value, true);
}
function syntaxHighlight(code, file)
{
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 };
function echoChar(c) {
if (c == '<')
result += '&lt;';
else if (c == '>')
result += '&gt;';
else if (c == '&')
result += '&amp;';
else if (c == '\t')
result += ' ';
else
result += c;
}
function isDigit(number) {
var string = "1234567890";
if (string.indexOf(number) != -1)
return true;
return false;
}
function isHex(hex) {
var string = "1234567890abcdefABCDEF";
if (string.indexOf(hex) != -1)
return true;
return false;
}
function isLetter(letter) {
var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (string.indexOf(letter) != -1)
return true;
return false;
}
var result = "";
var cPrev = "";
var c = "";
var cNext = "";
for (var i = 0; i < code.length; i++) {
cPrev = c;
c = code.charAt(i);
cNext = code.charAt(i + 1);
if (c == "/" && cNext == "*") {
result += "<span class=\"comment\">";
echoChar(c);
echoChar(cNext);
for (i += 2; i < code.length; i++) {
c = code.charAt(i);
if (c == "\n")
result += "</span>";
echoChar(c);
if (c == "\n")
result += "<span class=\"comment\">";
if (cPrev == "*" && c == "/")
break;
cPrev = c;
}
result += "</span>";
continue;
} else if (c == "/" && cNext == "/") {
result += "<span class=\"comment\">";
echoChar(c);
echoChar(cNext);
for (i += 2; i < code.length; i++) {
c = code.charAt(i);
if (c == "\n")
break;
echoChar(c);
}
result += "</span>";
echoChar(c);
continue;
} else if (c == "\"" || c == "'") {
var instringtype = c;
var stringstart = i;
result += "<span class=\"string\">";
echoChar(c);
for (i += 1; i < code.length; i++) {
c = code.charAt(i);
if (stringstart < (i - 1) && cPrev == instringtype && code.charAt(i - 2) != "\\")
break;
echoChar(c);
cPrev = c;
}
result += "</span>";
echoChar(c);
continue;
} else if (c == "0" && cNext == "x" && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
result += "<span class=\"number\">";
echoChar(c);
echoChar(cNext);
for (i += 2; i < code.length; i++) {
c = code.charAt(i);
if (!isHex(c))
break;
echoChar(c);
}
result += "</span>";
echoChar(c);
continue;
} else if ((isDigit(c) || ((c == "-" || c == ".") && isDigit(cNext))) && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
result += "<span class=\"number\">";
echoChar(c);
for (i += 1; i < code.length; i++) {
c = code.charAt(i);
if (!isDigit(c) && c != ".")
break;
echoChar(c);
}
result += "</span>";
echoChar(c);
continue;
} else if (isLetter(c) && (i == 0 || !isLetter(cPrev))) {
var keyword = c;
var cj = "";
for (var j = i + 1; j < i + 12 && j < code.length; j++) {
cj = code.charAt(j);
if (!isLetter(cj))
break;
keyword += cj;
}
if (keywords[keyword]) {
var functionName = "";
var functionIsAnonymous = false;
if (keyword == "function") {
var functionKeywordOffset = 8;
for (var j = i + functionKeywordOffset; j < code.length; j++) {
cj = code.charAt(j);
if (cj == " ")
continue;
if (cj == "(")
break;
functionName += cj;
}
if (!functionName.length) {
functionIsAnonymous = true;
var functionAssignmentFound = false;
var functionNameStart = -1;
var functionNameEnd = -1;
for (var j = i - 1; j >= 0; j--) {
cj = code.charAt(j);
if (cj == ":" || cj == "=") {
functionAssignmentFound = true;
continue;
}
var curCharIsSpace = (cj == " " || cj == "\t" || cj == "\n");
if (functionAssignmentFound && functionNameEnd == -1 && !curCharIsSpace) {
functionNameEnd = j + 1;
} else if (!functionAssignmentFound && !curCharIsSpace) {
break;
} else if (functionNameEnd != -1 && curCharIsSpace) {
functionNameStart = j;
break;
}
}
if (functionNameStart != -1 && functionNameEnd != -1)
functionName = code.substring(functionNameStart, functionNameEnd);
}
if (!functionName.length)
functionName = "function";
file.functionNames.push(functionName);
}
var fileIndex = filesLookup[file.url];
if (keyword == "function" && functionIsAnonymous)
result += "<span class=\"keyword\"><a name=\"function-" + fileIndex + "-" + file.functionNames.length + "\" id=\"" + fileIndex + "-" + file.functionNames.length + "\">" + keyword + "</a></span>";
else
result += "<span class=\"keyword\">" + keyword + "</span>";
if (functionName.length && !functionIsAnonymous) {
result += " <a name=\"function-" + fileIndex + "-" + file.functionNames.length + "\" id=\"" + fileIndex + "-" + file.functionNames.length + "\">" + functionName + "</a>";
i += keyword.length + functionName.length;
} else
i += keyword.length - 1;
continue;
}
}
echoChar(c);
}
return result;
}
function navFilePrevious(element)
{
if (element.disabled)
return;
var lastFile = previousFiles.pop();
if (currentFile != -1)
nextFiles.unshift(currentFile);
loadFile(lastFile, false);
}
function navFileNext(element)
{
if (element.disabled)
return;
var lastFile = nextFiles.shift();
if (currentFile != -1)
previousFiles.push(currentFile);
loadFile(lastFile, false);
}
function updateFunctionStack()
{
var stackframeTable = document.getElementById("stackframeTable");
stackframeTable.innerHTML = ""; // clear the content
currentStack = new Array();
var stack = DebuggerDocument.currentFunctionStack();
for(var i = 0; i < stack.length; i++) {
var tr = document.createElement("tr");
var td = document.createElement("td");
td.className = "stackNumber";
td.innerText = i;
tr.appendChild(td);
td = document.createElement("td");
td.innerText = stack[i];
tr.appendChild(td);
tr.addEventListener("click", selectStackFrame, true);
stackframeTable.appendChild(tr);
var frame = new ScriptCallFrame(stack[i], i, tr);
tr.callFrame = frame;
currentStack.push(frame);
if (i == 0) {
tr.addStyleClass("current");
frame.loadVariables();
currentCallFrame = frame;
}
}
}
function selectStackFrame(event)
{
var stackframeTable = document.getElementById("stackframeTable");
var rows = stackframeTable.childNodes;
for (var i = 0; i < rows.length; i++)
rows[i].removeStyleClass("current");
this.addStyleClass("current");
this.callFrame.loadVariables();
currentCallFrame = this.callFrame;
if (frameLineNumberInfo = frameLineNumberStack[this.callFrame.index - 1])
jumpToLine(frameLineNumberInfo[0], frameLineNumberInfo[1]);
else if (this.callFrame.index == 0)
jumpToLine(lastStatement[0], lastStatement[1]);
}
function selectVariable(event)
{
var variablesTable = document.getElementById("variablesTable");
var rows = variablesTable.childNodes;
for (var i = 0; i < rows.length; i++)
rows[i].removeStyleClass("current");
this.addStyleClass("current");
}
function switchFunction(index, shouldResetPopup)
{
if (shouldResetPopup === undefined) shouldResetPopup = false;
var sourcesFrame = window.frames['sourcesFrame'];
if (shouldResetPopup || index == 0) {
document.getElementById("functionPopupButtonContent").innerHTML = '<span class="placeholder">&lt;No selected symbol&gt;</span>';
return;
}
var functionSelect = document.getElementById("functions");
var selectedFunction = functionSelect.childNodes[index];
var selection = sourcesFrame.getSelection();
var currentFunction = selectedFunction.value;
var currentFunctionElement = sourcesFrame.document.getElementById(currentFunction);
functionSelect.blur();
sourcesFrame.focus();
selection.setBaseAndExtent(currentFunctionElement, 0, currentFunctionElement, 1);
sourcesFrame.location.hash = "#function-" + selectedFunction.value;
document.getElementById("functionPopupButtonContent").innerText = selectedFunction.innerText;
}
function loadFile(fileIndex, manageNavLists)
{
var file = files[fileIndex];
if (!file)
return;
if (currentFile != -1 && files[currentFile] && files[currentFile].element)
files[currentFile].element.style.display = "none";
if (!file.loaded) {
var sourcesDocument = document.getElementById("sources").contentDocument;
var sourcesDiv = sourcesDocument.body;
var sourceDiv = sourcesDocument.createElement("div");
sourceDiv.id = "file" + fileIndex;
sourcesDiv.appendChild(sourceDiv);
file.element = sourceDiv;
var table = sourcesDocument.createElement("table");
sourceDiv.appendChild(table);
var normalizedSource = file.source.replace(/\r\n|\r/g, "\n"); // normalize line endings
var lines = syntaxHighlight(normalizedSource, file).split("\n");
for( var i = 0; i < lines.length; i++ ) {
var tr = sourcesDocument.createElement("tr");
var td = sourcesDocument.createElement("td");
td.className = "gutter";
td.title = (i + 1);
td.addEventListener("click", breakpointAction, true);
td.addEventListener("dblclick", function(event) { toggleBreakpointEditorOnLine(event.target.title); }, true);
td.addEventListener("mousedown", moveBreakPoint, true);
tr.appendChild(td);
td = sourcesDocument.createElement("td");
td.className = "source";
td.innerHTML = (lines[i].length ? lines[i] : "&nbsp;");
tr.appendChild(td);
table.appendChild(tr);
}
file.loaded = true;
}
file.element.style.removeProperty("display");
document.getElementById("filesPopupButtonContent").innerText = (file.url ? file.url : "(unknown script)");
var filesSelect = document.getElementById("files");
for (var i = 0; i < filesSelect.childNodes.length; i++) {
if (filesSelect.childNodes[i].value == fileIndex) {
filesSelect.selectedIndex = i;
break;
}
}
// Populate the function names pop-up
if (file.functionNames.length > 0) {
var functionSelect = document.getElementById("functions");
var functionOption = document.createElement("option");
document.getElementById("functionNamesPopup").style.display = "inline";
switchFunction(0, true);
functionSelect.removeChildren();
functionOption.value = null;
functionOption.text = "<No selected symbol>";
functionSelect.appendChild(functionOption);
for (var i = 0; i < file.functionNames.length; i++) {
functionOption = document.createElement("option");
functionOption.value = fileIndex + "-" + (i+1);
functionOption.text = file.functionNames[i] + "()";
functionSelect.appendChild(functionOption);
}
} else
document.getElementById("functionNamesPopup").style.display = "none";
if (manageNavLists) {
nextFiles = new Array();
if (currentFile != -1)
previousFiles.push(currentFile);
}
document.getElementById("navFileLeftButton").disabled = (previousFiles.length == 0);
document.getElementById("navFileRightButton").disabled = (nextFiles.length == 0);
//Remember and recall scroll position for current file and file we just loaded
var frameBody = document.getElementById("sources").contentDocument.body;
if (currentFile != -1)
files[currentFile].scrollPosition = frameBody.scrollTop;
frameBody.scrollTop = file.scrollPosition;
frameBody.scrollLeft = 0;
currentFile = fileIndex;
}
function updateFileSource(source, url, force)
{
var fileIndex = filesLookup[url];
if (!fileIndex || !source.length)
return;
var file = files[fileIndex];
if (force || file.source.length != source.length || file.source != source) {
file.source = source;
file.loaded = false;
if (file.element) {
file.element.parentNode.removeChild(file.element);
file.element = null;
}
if (currentFile == fileIndex || force)
loadFile(fileIndex, false);
}
}
/**
* ParsedURL - this object chops up full URL into two parts:
* 1) The domain: everything from http:// to the end of the domain name
* 2) The relative path: everything after the domain
*
* @param string url URL to be processed
*/
function ParsedURL(url)
{
// Since we're getting the URL from the browser, we're safe to assume the URL is already well formatted
// and so there is no need for more sophisticated regular expression here
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})?)\/?(.*)/);
// the domain here is considered the whole http://www.example.org:8000 or file:///Users/user/folder/file.htm string for display purposes
this.domain = url_parts[1];
// the relative path is everything following the domain
this.relativePath = (url_parts[5] === undefined) ? "/" + url_parts[3] : "/" + url_parts[5];
}
/**
* SiteBrowser - modifies the file tree via DOM as new files are being open
*
*/
function SiteBrowser()
{
var fileBrowser = document.getElementById("filesBrowserSites");
this.addURL = function add(url, fileIndex)
{
var parsedURL = new ParsedURL(url);
var divs = fileBrowser.getElementsByTagName("div");
if (divs.length == 0) {
addNewDomain(parsedURL, fileIndex);
} else {
var isNew = true;
for (var i = 0; i < divs.length; i++) {
if (divs[i].id == parsedURL.domain) {
var uls = divs[i].getElementsByTagName("ul");
var ul = (uls.length > 0) ? uls[0] : document.createElement("ul");
var li = document.createElement("li");
li.id = fileIndex;
li.addEventListener("click", fileBrowserMouseEvents, false);
li.title = li.innerText = parsedURL.relativePath ? parsedURL.relativePath : "/";
ul.appendChild(li);
isNew = false;
break;
}
}
if (isNew) {
addNewDomain(parsedURL, fileIndex);
}
}
}
this.selectInitialFile = function sf()
{
if (currentFile == -1)
document.getElementById("1").className = "active";
}
function addNewDomain(parsedURL, fileIndex)
{
var div = document.createElement("div");
var ul = document.createElement("ul");
var li = document.createElement("li");
div.id = div.innerText = div.title = parsedURL.domain;
div.addEventListener("click", fileBrowserMouseEvents, false);
// Maybe we can add some roll-overs here...
//div.addEventListener("mouseover", fileBrowserMouseEvents, false);
//div.addEventListener("mouseout", fileBrowserMouseEvents, false);
li.id = fileIndex;
li.addEventListener("click", fileBrowserMouseEvents, false);
li.title = li.innerText = parsedURL.relativePath ? parsedURL.relativePath : "/";
ul.appendChild(li);
div.appendChild(ul);
fileBrowser.appendChild(div);
}
function removeFile(fileIndex)
{
var theFile = document.getElementById(fileIndex);
// If we are removing the last file from its site, go ahead and remove the whole site
if (theFile.parentNode.childNodes.length < 2) {
var theSite = theFile.parentNode.parentNode;
theSite.removeChildren();
theSite.parentNode.removeChild(theSite);
}
else
theFile.parentNode.removeChild(theFile);
}
}
function fileBrowserMouseEvents(event)
{
switch (event.type)
{
case "click":
// If we clicked on a site, collapse/expand it, if on a file, display it. Since we're only capturing this
// event from either a DIV or LI element, we don't have to worry about any ambiguity
(event.target.nodeName.toUpperCase() == "DIV") ? toggleCollapseSite(event) : fileClicked(event.target.id);
break;
}
}
function fileClicked(fileId, shouldLoadFile)
{
if (shouldLoadFile === undefined)
shouldLoadFile = true;
if (currentFile != -1)
document.getElementById(currentFile).className = "passive";
document.getElementById(fileId).className = "active";
if (shouldLoadFile)
loadFile(fileId, false);
}
function toggleCollapseSite(event)
{
var thisSite = document.getElementById(event.target.id);
var siteFiles = thisSite.getElementsByTagName("ul");
if (siteFiles[0].style.display == "block" || !siteFiles[0].style.display) {
siteFiles[0].style.display = "none";
thisSite.className = "collapsed";
} else {
siteFiles[0].style.display = "block";
thisSite.className = "expanded";
}
}
function didParseScript(source, fileSource, url, sourceId, baseLineNumber)
{
var fileIndex = filesLookup[url];
var file = files[fileIndex];
var firstLoad = false;
if (!fileIndex || !file) {
fileIndex = files.length + 1;
if (url.length)
filesLookup[url] = fileIndex;
file = new Object();
file.scripts = new Array();
file.breakpoints = new Array();
file.functionNames = new Array();
file.source = (fileSource.length ? fileSource : source);
file.url = (url.length ? url : null);
file.loaded = false;
files[fileIndex] = file;
var filesSelect = document.getElementById("files");
var option = document.createElement("option");
files[fileIndex].menuOption = option;
option.value = fileIndex;
option.text = (file.url ? file.url : "(unknown script)");
filesSelect.appendChild(option);
var siteBrowser = new SiteBrowser();
siteBrowser.addURL(file.url, fileIndex);
siteBrowser.selectInitialFile();
firstLoad = true;
}
var sourceObj = new Object();
sourceObj.file = fileIndex;
sourceObj.baseLineNumber = baseLineNumber;
file.scripts.push(sourceId);
scripts[sourceId] = sourceObj;
if (!firstLoad)
updateFileSource((fileSource.length ? fileSource : source), url, false);
if (currentFile == -1)
loadFile(fileIndex, false);
}
function jumpToLine(sourceId, line)
{
var script = scripts[sourceId];
if (line <= 0 || !script)
return;
var file = files[script.file];
if (!file)
return;
if (currentFile != script.file)
loadFile(script.file, true);
if (currentRow)
currentRow.removeStyleClass("current");
if (!file.element)
return;
if (line > file.element.firstChild.childNodes.length)
return;
currentRow = file.element.firstChild.childNodes.item(line - 1);
if (!currentRow)
return;
currentRow.addStyleClass("current");
var sourcesDiv = document.getElementById("sources");
var sourcesDocument = document.getElementById("sources").contentDocument;
var parent = sourcesDocument.body;
var offset = totalOffsetTop(currentRow, parent);
if (offset < (parent.scrollTop + 20) || offset > (parent.scrollTop + sourcesDiv.clientHeight - 20))
parent.scrollTop = totalOffsetTop(currentRow, parent) - (sourcesDiv.clientHeight / 2) + 10;
}
function willExecuteStatement(sourceId, line, fromLeavingFrame)
{
var script = scripts[sourceId];
if (line <= 0 || !script)
return;
var file = files[script.file];
if (!file)
return;
lastStatement = [sourceId, line];
var breakpoint = file.breakpoints[line];
var shouldBreak = false;
if (breakpoint && breakpoint.enabled) {
switch(breakpoint.type) {
case 0:
shouldBreak = (breakpoint.value == "break" || DebuggerDocument.evaluateScript(breakpoint.value, 0) == 1);
if (shouldBreak)
breakpoint.hitcount++;
break;
case 1:
var message = "Hit breakpoint on line " + line;
if (breakpoint.value != "break")
message = DebuggerDocument.evaluateScript(breakpoint.value, 0);
if (consoleWindow)
consoleWindow.appendMessage("", message);
breakpoint.hitcount++;
break;
}
var editor = breakpoint.editor;
var counter = null;
if (editor)
counter = breakpoint.editor.query('.//span[@class="hitCounter"]');
if (counter)
counter.innerText = breakpoint.hitcount;
}
if (pauseOnNextStatement || shouldBreak || (steppingOver && !steppingStack)) {
pause();
pauseOnNextStatement = false;
pausedWhileLeavingFrame = fromLeavingFrame || false;
}
if (isPaused) {
updateFunctionStack();
jumpToLine(sourceId, line);
}
}
function didEnterCallFrame(sourceId, line)
{
if (steppingOver || steppingOut)
steppingStack++;
if (lastStatement)
frameLineNumberStack.unshift(lastStatement);
willExecuteStatement(sourceId, line);
}
function willLeaveCallFrame(sourceId, line)
{
if (line <= 0)
resume();
willExecuteStatement(sourceId, line, true);
frameLineNumberStack.shift();
if (!steppingStack)
steppingOver = false;
if (steppingOut && !steppingStack) {
steppingOut = false;
pauseOnNextStatement = true;
}
if ((steppingOver || steppingOut) && steppingStack >= 1)
steppingStack--;
}
function exceptionWasRaised(sourceId, line)
{
pause();
updateFunctionStack();
jumpToLine(sourceId, line);
}
function showConsoleWindow()
{
if (!consoleWindow)
consoleWindow = window.open("console.html", "console", "top=200, left=200, width=500, height=300, toolbar=yes, resizable=yes");
else
consoleWindow.focus();
}
function closeFile(fileIndex)
{
if (fileIndex != -1) {
currentFile = -1;
var file = files[fileIndex];
var sourcesDocument = document.getElementById("sources").contentDocument;
// Clean up our file's content
sourcesDocument.getElementById("file" + fileIndex).removeChildren();
// Remove the file from the open files pool
delete sourcesDocument.getElementById("file" + fileIndex);
var filesSelect = document.getElementById("files");
// Select the next loaded file. If we're at the end of the list, loop back to beginning.
var nextSelectedFile = (filesSelect.selectedIndex + 1 >= filesSelect.options.length) ? 0 : filesSelect.selectedIndex;
// Remove the file from file lists
filesSelect.options[filesSelect.selectedIndex] = null;
SiteBrowser.removeFile(fileIndex);
delete files[fileIndex];
// Clean up the function list
document.getElementById("functions").removeChildren();
// Display appropriate place-holders, if we closed all files
if (filesSelect.options.length < 1) {
document.getElementById("filesPopupButtonContent").innerHTML = "<span class='placeholder'>&lt;No files loaded&gt;</span>";
document.getElementById("functionNamesPopup").style.display = "none";
}
else
switchFile(nextSelectedFile);
}
}
function closeCurrentFile()
{
closeFile(currentFile);
}