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