d0696d66813fec8407a55d16f3c0185f3235c66e
[WebKit-https.git] / WebKitTools / Drosera / debugger.js
1 /*
2  * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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. 
16  *
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.
27  */
28
29 var sourceFiles = new Array();
30 var currentSourceId = -1;
31 var currentRow = null;
32 var currentStack = null;
33 var previousFiles = new Array();
34 var nextFiles = new Array();
35 var isResizingColumn = false;
36
37 function sleep(numberMillis) {
38     var now = new Date();
39     var exitTime = now.getTime() + numberMillis;
40     while (true) {
41         now = new Date();
42         if (now.getTime() > exitTime)
43             return;
44     }
45 }
46
47 function columnResizerMouseOver(element) {
48     element.style.cursor = "move";
49 }
50
51 function columnResizerMouseOut(element) {
52
53     element.style.cursor = "arrow";
54 }
55
56 function headerMouseDown(element) {
57     if (!isResizingColumn) 
58         element.style.background = "url(glossyHeaderPressed.png) repeat-x";
59 }
60
61 function headerMouseUp(element) {
62     element.style.background = "url(glossyHeader.png) repeat-x";
63 }
64
65 function headerMouseOut(element) {
66     element.style.background = "url(glossyHeader.png) repeat-x";
67 }
68
69 function dividerDragStart(element, dividerDrag, dividerDragEnd, event) {
70     element.dragging = true;
71     element.dragLastY = event.clientY + window.scrollY;
72     element.dragLastX = event.clientX + window.scrollX;
73     document.addEventListener("mousemove", dividerDrag, true);
74     document.addEventListener("mouseup", dividerDragEnd, true);
75     event.preventDefault();
76 }
77
78 function sourceDividerDragStart(event) {
79     dividerDragStart(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd, event);
80 }
81
82 function infoDividerDragStart(event) {
83     dividerDragStart(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd, event);
84 }
85
86 function columnResizerDragStart(event) {
87     isResizingColumn = true;
88     dividerDragStart(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
89 }
90
91 function columnResizerDragEnd(event) {
92     isResizingColumn = false;
93     dividerDragEnd(document.getElementById("variableColumnResizer"), columnResizerDrag, columnResizerDragEnd, event);
94 }
95
96 function infoDividerDragEnd(event) {
97     dividerDragEnd(document.getElementById("infoDivider"), infoDividerDrag, infoDividerDragEnd);
98 }
99
100 function sourceDividerDragEnd(event) {
101     dividerDragEnd(document.getElementById("divider"), dividerDrag, sourceDividerDragEnd);
102 }
103
104 function dividerDragEnd(element, dividerDrag, dividerDragEnd, event) {
105     element.dragging = false;
106     document.removeEventListener("mousemove", dividerDrag, true);
107     document.removeEventListener("mouseup", dividerDragEnd, true);
108 }
109
110 function columnResizerDrag(event) {
111     var element = document.getElementById("variableColumnResizer");
112     if (element.dragging == true) {
113         var main = document.getElementById("rightPane");
114         var variableColumn = document.getElementById("variable");
115         var x = event.clientX + window.scrollX;
116         var delta = element.dragLastX - x;
117         var newWidth = constrainedWidthFromElement(variableColumn.clientWidth - delta, main);
118         variableColumn.style.width = newWidth + "px";
119         element.style.left = newWidth + "px";
120         element.dragLastX = x;
121         event.preventDefault();
122     }
123 }
124
125 function constrainedWidthFromElement(width, element) {
126     if (width < element.clientWidth * 0.25)
127         width = element.clientWidth * 0.25;
128     else if (width > element.clientWidth * 0.75)
129         width = element.clientWidth * 0.75;
130
131     return width;
132 }
133
134 function constrainedHeightFromElement(height, element) {
135     if (height < element.clientHeight * 0.25)
136         height = element.clientHeight * 0.25;
137     else if (height > element.clientHeight * 0.75)
138         height = element.clientHeight * 0.75;
139
140     return height;
141 }
142
143 function infoDividerDrag(event) {
144     var element = document.getElementById("infoDivider");
145     if (document.getElementById("infoDivider").dragging == true) {
146         var main = document.getElementById("main");
147         var leftPane = document.getElementById("leftPane");
148         var rightPane = document.getElementById("rightPane");
149         var x = event.clientX + window.scrollX;
150         var delta = element.dragLastX - x;
151         var newWidth = constrainedWidthFromElement(leftPane.clientWidth - delta, main);
152         leftPane.style.width = newWidth + "px";
153         rightPane.style.left = newWidth + "px";
154         element.dragLastX = x;
155         event.preventDefault();
156     }
157 }
158
159 function dividerDrag(event) {
160     var element = document.getElementById("divider");
161     if (document.getElementById("divider").dragging == true) {
162         var main = document.getElementById("main");
163         var top = document.getElementById("info");
164         var bottom = document.getElementById("body");
165         var y = event.clientY + window.scrollY;
166         var delta = element.dragLastY - y;
167         var newHeight = constrainedHeightFromElement(top.clientHeight - delta, main);
168         top.style.height = newHeight + "px";
169         bottom.style.top = newHeight + "px";
170         element.dragLastY = y;
171         event.preventDefault();
172     }
173 }
174
175 function loaded() {
176     document.getElementById("divider").addEventListener("mousedown", sourceDividerDragStart, false);
177     document.getElementById("infoDivider").addEventListener("mousedown", infoDividerDragStart, false);
178     document.getElementById("variableColumnResizer").addEventListener("mousedown", columnResizerDragStart, false);
179 }
180
181 function isPaused() {
182     return DebuggerDocument.isPaused();
183 }
184
185 function pause() {
186     DebuggerDocument.pause();
187 }
188
189 function resume()
190 {
191     if (currentRow) {
192         removeStyleClass(currentRow, "current");
193         currentRow = null;
194     }
195
196     if (currentStack) {
197         var stackframeTable = document.getElementById("stackframeTable");
198         stackframeTable.innerHTML = ""; // clear the content
199         currentStack = null;
200     }
201
202     DebuggerDocument.resume();
203 }
204
205 function stepInto()
206 {
207     DebuggerDocument.stepInto();
208 }
209
210 function hasStyleClass(element,className)
211 {
212     return ( element.className.indexOf(className) != -1 );
213 }
214
215 function addStyleClass(element,className)
216 {
217     if (!hasStyleClass(element,className))
218         element.className += ( element.className.length ? " " + className : className );
219 }
220
221 function removeStyleClass(element,className)
222 {
223     if (hasStyleClass(element,className))
224         element.className = element.className.replace(className, "");
225 }
226
227 function addBreakPoint(event)
228 {
229     var row = event.target.parentNode;
230     if (hasStyleClass(row, "breakpoint")) {
231         if (hasStyleClass(row, "disabled")) {
232             removeStyleClass(row, "disabled");
233             sourceFiles[currentSourceId].breakpoints[parseInt(event.target.title)] = 1;
234         } else {
235             addStyleClass(row, "disabled");
236             sourceFiles[currentSourceId].breakpoints[parseInt(event.target.title)] = -1;
237         }
238     } else {
239         addStyleClass(row, "breakpoint");
240         removeStyleClass(row, "disabled");
241         sourceFiles[currentSourceId].breakpoints[parseInt(event.target.title)] = 1;
242     }
243 }
244
245 function totalOffsetTop(element,stop)
246 {
247     var currentTop = 0;
248     if (element.offsetParent) {
249         while (element.offsetParent) {
250             currentTop += element.offsetTop
251             element = element.offsetParent;
252             if (element == stop)
253                 break;
254         }
255     }
256     return currentTop;
257 }
258
259 function switchFile()
260 {
261     var files = document.getElementById("files");
262     loadSource(files.options[files.selectedIndex].value,true);
263 }
264
265 function syntaxHighlight(code)
266 {
267     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 };
268
269     function echoChar(c) {
270         if (c == '<')
271             result += '&lt;';
272         else if (c == '>')
273             result += '&gt;';
274         else if (c == '&')
275             result += '&amp;';
276         else if (c == '\t')
277             result += '    ';
278         else
279             result += c;
280     }
281
282     function isDigit(number) {
283         var string = "1234567890";
284         if (string.indexOf(number) != -1)
285             return true;
286         return false;
287     }
288
289     function isHex(hex) {
290         var string = "1234567890abcdefABCDEF";
291         if (string.indexOf(hex) != -1)
292             return true;
293         return false;
294     }
295
296     function isLetter(letter) {
297         var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
298         if (string.indexOf(letter) != -1)
299             return true;
300         return false;
301     }
302
303     var result = "";
304     var cPrev = "";
305     var c = "";
306     var cNext = "";
307     for (var i = 0; i < code.length; i++) {
308         cPrev = c;
309         c = code.charAt(i);
310         cNext = code.charAt(i + 1);
311
312         if (c == "/" && cNext == "*") {
313             result += "<span class=\"comment\">";
314             echoChar(c);
315             echoChar(cNext);
316             for (i += 2; i < code.length; i++) {
317                 c = code.charAt(i);
318                 if (c == "\n")
319                     result += "</span>";
320                 echoChar(c);
321                 if (c == "\n")
322                     result += "<span class=\"comment\">";
323                 if (cPrev == "*" && c == "/")
324                     break;
325                 cPrev = c;
326             }
327             result += "</span>";
328             continue;
329         } else if (c == "/" && cNext == "/") {
330             result += "<span class=\"comment\">";
331             echoChar(c);
332             echoChar(cNext);
333             for (i += 2; i < code.length; i++) {
334                 c = code.charAt(i);
335                 if (c == "\n")
336                     break;
337                 echoChar(c);
338             }
339             result += "</span>";
340             echoChar(c);
341             continue;
342         } else if (c == "\"" || c == "'") {
343             var instringtype = c;
344             var stringstart = i;
345             result += "<span class=\"string\">";
346             echoChar(c);
347             for (i += 1; i < code.length; i++) {
348                 c = code.charAt(i);
349                 if (stringstart < (i - 1) && cPrev == instringtype && code.charAt(i - 2) != "\\")
350                     break;
351                 echoChar(c);
352                 cPrev = c;
353             }
354             result += "</span>";
355             echoChar(c);
356             continue;
357         } else if (c == "0" && cNext == "x" && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
358             result += "<span class=\"number\">";
359             echoChar(c);
360             echoChar(cNext);
361             for (i += 2; i < code.length; i++) {
362                 c = code.charAt(i);
363                 if (!isHex(c))
364                     break;
365                 echoChar(c);
366             }
367             result += "</span>";
368             echoChar(c);
369             continue;
370         } else if ((isDigit(c) || ((c == "-" || c == ".") && isDigit(cNext))) && (i == 0 || (!isLetter(cPrev) && !isDigit(cPrev)))) {
371             result += "<span class=\"number\">";
372             echoChar(c);
373             for (i += 1; i < code.length; i++) {
374                 c = code.charAt(i);
375                 if (!isDigit(c) && c != ".")
376                     break;
377                 echoChar(c);
378             }
379             result += "</span>";
380             echoChar(c);
381             continue;
382         } else if(isLetter(c) && (i == 0 || !isLetter(cPrev))) {
383             var keyword = c;
384             var cj = "";
385             for (var j = i + 1; j < i + 12 && j < code.length; j++) {
386                 cj = code.charAt(j);
387                 if (!isLetter(cj))
388                     break;
389                 keyword += cj;
390             }
391
392             if (keywords[keyword]) {
393                 result += "<span class=\"keyword\">" + keyword + "</span>";
394                 i += keyword.length - 1;
395                 continue;
396             }
397         }
398
399         echoChar(c);
400     }
401
402     return result;
403 }
404
405 function navFilePrevious(element)
406 {
407     if (element.disabled)
408         return;
409     var lastSource = previousFiles.pop();
410     if (currentSourceId != -1)
411         nextFiles.unshift(currentSourceId);
412     loadSource(lastSource, false);
413 }
414
415 function navFileNext(element)
416 {
417     if (element.disabled)
418         return;
419     var lastSource = nextFiles.shift();
420     if (currentSourceId != -1)
421         previousFiles.push(currentSourceId);
422     loadSource(lastSource, false);
423 }
424
425 function updateFunctionStack()
426 {
427     var stackframeTable = document.getElementById("stackframeTable");
428     stackframeTable.innerHTML = ""; // clear the content
429
430     currentStack = DebuggerDocument.currentFunctionStack();
431     for(var i = 0; i < currentStack.length; i++) {
432         var tr = document.createElement("tr");
433         var td = document.createElement("td");
434         td.className = "stackNumber";
435         td.innerText = i;
436         tr.appendChild(td);
437
438         td = document.createElement("td");
439         td.innerText = currentStack[i];
440         tr.appendChild(td);
441
442         stackframeTable.appendChild(tr);
443     }
444
445     var tr = document.createElement("tr");
446     var td = document.createElement("td");
447     td.className = "stackNumber";
448     td.innerText = i;
449     tr.appendChild(td);
450
451     td = document.createElement("td");
452     td.innerText = "Global";
453     tr.appendChild(td);
454
455     stackframeTable.appendChild(tr);
456 }
457
458 function loadSource(sourceId,manageNavLists)
459 {
460     if (!sourceFiles[sourceId])
461         return;
462
463     if (currentSourceId != -1 && sourceFiles[currentSourceId] && sourceFiles[currentSourceId].element)
464         sourceFiles[currentSourceId].element.style.display = "none";
465
466     if (!sourceFiles[sourceId].loaded) {
467         sourceFiles[sourceId].lines = sourceFiles[sourceId].source.split("\n");
468
469         var sourcesDocument = document.getElementById("sources").contentDocument;
470         var sourcesDiv = sourcesDocument.body;
471         var sourceDiv = sourcesDocument.createElement("div");
472         sourceDiv.id = "source" + sourceId;
473         sourcesDiv.appendChild(sourceDiv);
474         sourceFiles[sourceId].element = sourceDiv;
475
476         var table = sourcesDocument.createElement("table");
477         sourceDiv.appendChild(table);
478
479         var lines = syntaxHighlight(sourceFiles[sourceId].source).split("\n");
480         for( var i = 0; i < lines.length; i++ ) {
481             var tr = sourcesDocument.createElement("tr");
482             var td = sourcesDocument.createElement("td");
483             td.className = "gutter";
484             td.title = (i + 1);
485             td.addEventListener("click", addBreakPoint, true);
486             tr.appendChild(td);
487
488             td = sourcesDocument.createElement("td");
489             td.className = "source";
490             td.innerHTML = lines[i];
491             tr.appendChild(td);
492             table.appendChild(tr);
493         }
494
495         sourceFiles[sourceId].loaded = true;
496     }
497
498     sourceFiles[sourceId].element.style.display = null;
499
500     document.getElementById("filesPopupButtonContent").innerText = sourceFiles[sourceId].url;
501     
502     var files = document.getElementById("files");
503     for (var i = 0; i < files.childNodes.length; i++) {
504         if (files.childNodes[i].value == sourceId) {
505             files.selectedIndex = i;
506             break;
507         }
508     }
509
510     if (manageNavLists) {
511         nextFiles = new Array();
512         if (currentSourceId != -1)
513             previousFiles.push(currentSourceId);
514     }
515
516     document.getElementById("navFileLeftButton").disabled = (previousFiles.length == 0);
517     document.getElementById("navFileRightButton").disabled = (nextFiles.length == 0);
518
519     currentSourceId = sourceId;
520 }
521
522 function didParseScript(source,url,sourceId)
523 {
524     sourceFiles[sourceId] = new Object();
525     sourceFiles[sourceId].source = source;
526     sourceFiles[sourceId].url = url;
527     sourceFiles[sourceId].loaded = false;
528     sourceFiles[sourceId].breakpoints = new Array();
529
530     var files = document.getElementById("files");
531     var option = document.createElement("option");
532     sourceFiles[sourceId].menuOption = option;
533     option.value = sourceId;
534     option.text = url;
535     files.appendChild(option);
536
537     if (currentSourceId == -1)
538         loadSource(sourceId,true);
539     return true;
540 }
541
542 function willExecuteStatement(sourceId,line)
543 {
544     if (line <= 0 || !sourceFiles[sourceId])
545         return;
546
547     if (sourceFiles[sourceId].breakpoints[line] == 1)
548         pause();
549
550     if (isPaused()) {
551         if (currentSourceId != sourceId)
552             loadSource(sourceId,true);
553         if (currentRow)
554             removeStyleClass(currentRow, "current");
555         if (!sourceFiles[sourceId].element)
556             return;
557         if (sourceFiles[sourceId].element.firstChild.childNodes.length < line)
558             return;
559
560         updateFunctionStack();
561
562         currentRow = sourceFiles[sourceId].element.firstChild.childNodes.item(line - 1);
563         addStyleClass(currentRow, "current");
564
565         var sourcesDiv = document.getElementById("sources");
566         var sourcesDocument = document.getElementById("sources").contentDocument;
567         var parent = sourcesDocument.body;
568         var offset = totalOffsetTop(currentRow, parent);
569         if (offset < (parent.scrollTop + 20) || offset > (parent.scrollTop + sourcesDiv.clientHeight - 20))
570             parent.scrollTop = totalOffsetTop(currentRow, parent) - (sourcesDiv.clientHeight / 2) + 10;
571     }
572 }
573
574 function didEnterCallFrame(sourceId,line)
575 {
576     willExecuteStatement(sourceId,line);
577 }
578
579 function willLeaveCallFrame(sourceId,line)
580 {
581     willExecuteStatement(sourceId,line);
582 }