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