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