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