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