Changeset 51506 in webkit
- Timestamp:
- Nov 30, 2009 1:19:42 PM (14 years ago)
- Location:
- trunk
- Files:
-
- 1 added
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r51504 r51506 1 2009-11-30 Joseph Pecoraro <joepeck@webkit.org> 2 3 Reviewed by Pavel Feldman. 4 5 Web Inspector: Wrong console output for Regexp escape sequence 6 https://bugs.webkit.org/show_bug.cgi?id=31538 7 8 * inspector/console-format-expected.txt: 9 * inspector/console-format.html: 10 1 11 2009-11-30 Gustavo Noronha Silva <gustavo.noronha@collabora.co.uk> 2 12 -
trunk/LayoutTests/inspector/console-format-expected.txt
r50582 r51506 1 CONSOLE MESSAGE: line 9: Message format number %i, %d and %f 2 CONSOLE MESSAGE: line 10: Message %s for %s 3 CONSOLE MESSAGE: line 11: Object %o 4 CONSOLE MESSAGE: line 16: test,test2,,,test4 1 CONSOLE MESSAGE: line 12: Message format number %i, %d and %f 2 CONSOLE MESSAGE: line 13: Message %s for %s 3 CONSOLE MESSAGE: line 14: Object %o 4 CONSOLE MESSAGE: line 19: test,test2,,,test4 5 CONSOLE MESSAGE: line 49: /^url\(\s*(?:(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*')|(?:[!#$%&*-~\w]|(?:\\[\da-f]{1,6}\s?|\.))*)\s*\)/i 6 CONSOLE MESSAGE: line 50: /^url\(\s*(?:(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*')|(?:[!#$%&*-~\w]|(?:\\[\da-f]{1,6}\s?|\.))*)\s*\)/i 7 CONSOLE MESSAGE: line 49: /foo\\bar\sbaz/i 8 CONSOLE MESSAGE: line 50: /foo\\bar\sbaz/i 9 CONSOLE MESSAGE: line 49: test 10 CONSOLE MESSAGE: line 50: test 11 CONSOLE MESSAGE: line 49: Error: Unknown error 12 CONSOLE MESSAGE: line 50: Error: Unknown error 13 CONSOLE MESSAGE: line 49: [object HTMLBodyElement] 14 CONSOLE MESSAGE: line 50: [object HTMLBodyElement] 15 CONSOLE MESSAGE: line 49: function () { return 1; } 16 CONSOLE MESSAGE: line 50: function () { return 1; } 17 CONSOLE MESSAGE: line 49: function () { 18 return 2; 19 } 20 CONSOLE MESSAGE: line 50: function () { 21 return 2; 22 } 23 CONSOLE MESSAGE: line 49: 0.12 24 CONSOLE MESSAGE: line 50: 0.12 25 CONSOLE MESSAGE: line 49: http://webkit.org/ 26 CONSOLE MESSAGE: line 50: http://webkit.org/ 27 CONSOLE MESSAGE: line 49: null 28 CONSOLE MESSAGE: line 50: 29 CONSOLE MESSAGE: line 49: undefined 30 CONSOLE MESSAGE: line 50: 5 31 Tests that console logging dumps proper messages. 6 32 7 console-format.html:9Message format number 1, 2 and 3.5 8 console-format.html:10Message format for string 9 console-format.html:11Object Object 10 console-format.html:16["test", "test2", undefined, undefined, "test4"] 33 console-format.html:12Message format number 1, 2 and 3.5 34 console-format.html:13Message format for string 35 console-format.html:14Object Object 36 console-format.html:19["test", "test2", undefined, undefined, "test4"] 37 console-format.html:49/^url\(\s*(?:(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*')|(?:[!#$%&*-~\w]|(?:\\[\da-f]{1,6}\s?|\.))*)\s*\)/i 38 console-format.html:50[/^url\(\s*(?:(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*')|(?:[!#$%&*-~\w]|(?:\\[\da-f]{1,6}\s?|\.))*)\s*\)/i] 39 "/^url\(\s*(?:(?:\"(?:[^\\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*\"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*')|(?:[!#$%&*-~\w]|(?:\\[\da-f]{1,6}\s?|\.))*)\s*\)/i" 40 console-format.html:49/foo\\bar\sbaz/i 41 console-format.html:50[/foo\\bar\sbaz/i] 42 "/foo\\bar\sbaz/i" 43 console-format.html:49test 44 console-format.html:50["test"] 45 "test" 46 console-format.html:49Error: Unknown error 47 console-format.html:50[Error: Unknown error] 48 Error: Unknown error 49 console-format.html:49<body onload="onload()"> 50 console-format.html:50[<body onload="onload()">] 51 "HTMLBodyElement" 52 console-format.html:49function () { return 1; } 53 console-format.html:50[function () { return 1; }] 54 "function () { return 1; }" 55 console-format.html:49function () { return 2; } 56 console-format.html:50[function () {] 57 "function () {<br> return 2;<br> }" 58 console-format.html:490.12 59 console-format.html:50[0.12] 60 "0.12" 61 console-format.html:49http://webkit.org/ 62 console-format.html:50["http://webkit.org/"] 63 "http://webkit.org/" 64 console-format.html:49null 65 console-format.html:50[null] 66 "null" 67 console-format.html:49undefined 68 console-format.html:50[undefined] 69 "undefined" 11 70 -
trunk/LayoutTests/inspector/console-format.html
r50582 r51506 4 4 <script src="console-tests.js"></script> 5 5 <script> 6 7 // Global Values 8 var globals = []; 6 9 7 10 function doit() … … 16 19 console.log(array); 17 20 18 dumpConsoleMessages(); 21 // Populate Globals 22 var regex1 = /^url\(\s*(?:(?:"(?:[^\\\"]|(?:\\[\da-f]{1,6}\s?|\.))*"|'(?:[^\\\']|(?:\\[\da-f]{1,6}\s?|\.))*')|(?:[!#$%&*-~\w]|(?:\\[\da-f]{1,6}\s?|\.))*)\s*\)/i; 23 var regex2 = new RegExp("foo\\\\bar\\sbaz", "i"); 24 var str = "test"; 25 var error = new Error; 26 var node = document.body; 27 var func = function() { return 1; }; 28 var multilinefunc = function() { 29 return 2; 30 }; 31 var num = 1.2e-1; 32 var linkify = "http://webkit.org/"; 33 34 globals = [regex1, regex2, str, error, node, func, multilinefunc, num, linkify, null, undefined]; 35 loopOverGlobals(0); 36 } 37 38 function loopOverGlobals(current) 39 { 40 function advance() 41 { 42 var next = current + 1; 43 if (next == globals.length) 44 dumpConsoleMessages(); 45 else 46 loopOverGlobals(next); 47 } 48 49 console.log(globals[current]); 50 console.log([globals[current]]); 51 evaluateInWebInspector("frontend_evaluateGlobal", advance); 52 } 53 54 // Frontend functions. 55 56 function frontend_evaluateGlobal() 57 { 58 window.__next = window.__next || 0; 59 var current = window.__next++; 60 var expression = "globals[" + current + "]"; 61 frontend_evalExpression(expression); 62 } 63 64 function frontend_evalExpression(expression, isDate) 65 { 66 var self = WebInspector.console; 67 function printResult(result, exception) 68 { 69 if (typeof result.description === "string") 70 result = result.description.replace(/\n/g, "<br>"); 71 self.addMessage(new WebInspector.ConsoleCommandResult(result, exception, expression)); 72 } 73 74 self.evalInInspectedWindow(expression, "console", printResult); 19 75 } 20 76 -
trunk/WebCore/ChangeLog
r51501 r51506 1 2009-11-30 Joseph Pecoraro <joepeck@webkit.org> 2 3 Reviewed by Pavel Feldman. 4 5 Web Inspector: Wrong console output for Regexp escape sequence 6 https://bugs.webkit.org/show_bug.cgi?id=31538 7 8 Updated inspector/console-format.html 9 10 * inspector/front-end/ConsoleView.js: 11 (WebInspector.ConsoleView.createDividerElement): style issues. 12 (WebInspector.ConsoleView.createFilterElement): style issues. 13 (WebInspector.ConsoleView): added _customFormatters table. 14 (WebInspector.ConsoleView.prototype.updateMessageRepeatCount): style isses. 15 (WebInspector.ConsoleView.prototype._incrementErrorWarningCount): style issues. 16 (WebInspector.ConsoleView.prototype._format): simplified delegation to formatter logic. 17 (WebInspector.ConsoleView.prototype._formatobject): 18 (WebInspector.ConsoleView.prototype._formatnode): 19 (WebInspector.ConsoleView.prototype._printArray): 20 (WebInspector.ConsoleMessage.prototype._format): commented and broke down the algorithm into parts. 21 (WebInspector.ConsoleMessage.prototype._formatWithSubstitutionString.append): handle substitution string formatting. 22 (WebInspector.ConsoleMessage.prototype._formatIndividualValue): handling individual value formatting. 23 (WebInspector.ConsoleCommandResult): 24 * inspector/front-end/InjectedScript.js: simplified regex formatting. 25 * inspector/front-end/inspector.js: 26 (WebInspector.linkifyStringAsFragment): converted new RegExp to literal for performance benefits. 27 * inspector/front-end/utilities.js: 28 (Element.prototype.hasStyleClass): update inaccurate comment. 29 (String.prototype.trimURL): converted new RegExp to literal for performance benefits. 30 1 31 2009-11-30 Erik Arvidsson <arv@chromium.org> 2 32 -
trunk/WebCore/inspector/InspectorController.cpp
r51440 r51506 1869 1869 1870 1870 } // namespace WebCore 1871 1871 1872 1872 #endif // ENABLE(INSPECTOR) -
trunk/WebCore/inspector/front-end/ConsoleView.js
r51341 r51506 60 60 // Will hold the list of filter elements 61 61 this.filterBarElement = document.getElementById("console-filter"); 62 62 63 63 function createDividerElement() { 64 64 var dividerElement = document.createElement("div"); 65 66 65 dividerElement.addStyleClass("divider"); 67 68 66 this.filterBarElement.appendChild(dividerElement); 69 67 } 70 68 69 var updateFilterHandler = this._updateFilter.bind(this); 71 70 function createFilterElement(category) { 72 71 var categoryElement = document.createElement("li"); 73 72 categoryElement.category = category; 74 75 categoryElement.add StyleClass(categoryElement.category);76 73 categoryElement.addStyleClass(categoryElement.category); 74 categoryElement.addEventListener("click", updateFilterHandler, false); 75 77 76 var label = category.toString(); 78 77 categoryElement.appendChild(document.createTextNode(label)); 79 80 categoryElement.addEventListener("click", this._updateFilter.bind(this), false); 81 78 82 79 this.filterBarElement.appendChild(categoryElement); 83 80 return categoryElement; … … 85 82 86 83 this.allElement = createFilterElement.call(this, "All"); 87 88 84 createDividerElement.call(this); 89 90 85 this.errorElement = createFilterElement.call(this, "Errors"); 91 86 this.warningElement = createFilterElement.call(this, "Warnings"); … … 104 99 shortcut = WebInspector.KeyboardShortcut.makeKey("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl); 105 100 this._shortcuts[shortcut] = handler; 101 102 this._customFormatters = { 103 "object": this._formatobject, 104 "array": this._formatarray, 105 "node": this._formatnode 106 }; 106 107 } 107 108 … … 251 252 }, 252 253 253 updateMessageRepeatCount: function(count) { 254 updateMessageRepeatCount: function(count) 255 { 254 256 var msg = this.previousMessage; 255 257 var prevRepeatCount = msg.totalRepeatCount; … … 269 271 }, 270 272 271 _incrementErrorWarningCount: function(msg) { 273 _incrementErrorWarningCount: function(msg) 274 { 272 275 switch (msg.level) { 273 276 case WebInspector.ConsoleMessage.MessageLevel.Warning: … … 479 482 { 480 483 var isProxy = (output != null && typeof output === "object"); 481 482 if (forceObjectFormat) 483 var type = "object"; 484 else 485 var type = Object.proxyType(output); 486 487 if (isProxy && type !== "object" && type !== "function" && type !== "array" && type !== "node") { 488 // Unwrap primitive value, skip decoration. 489 output = output.description; 490 type = "undecorated" 491 } 492 493 // We don't perform any special formatting on these types, so we just 494 // pass them through the simple _formatvalue function. 495 var undecoratedTypes = { 496 "undefined": 1, 497 "null": 1, 498 "boolean": 1, 499 "number": 1, 500 "undecorated": 1 501 }; 502 503 var formatter; 504 if (forceObjectFormat) 505 formatter = "_formatobject"; 506 else if (type in undecoratedTypes) 507 formatter = "_formatvalue"; 508 else { 509 formatter = "_format" + type; 510 if (!(formatter in this)) { 511 formatter = "_formatobject"; 512 type = "object"; 513 } 484 var type = (forceObjectFormat ? "object" : Object.proxyType(output)); 485 486 var formatter = this._customFormatters[type]; 487 if (!formatter || !isProxy) { 488 formatter = this._formatvalue; 489 output = output.description || output; 490 type = "undecorated"; 514 491 } 515 492 516 493 var span = document.createElement("span"); 517 494 span.addStyleClass("console-formatted-" + type); 518 this[formatter](output, span);495 formatter.call(this, output, span); 519 496 return span; 520 497 }, … … 525 502 }, 526 503 527 _formatfunction: function(func, elem) 528 { 529 elem.appendChild(document.createTextNode(func.description)); 530 }, 531 532 _formatdate: function(date, elem) 533 { 534 elem.appendChild(document.createTextNode(date)); 535 }, 536 537 _formatstring: function(str, elem) 538 { 539 elem.appendChild(document.createTextNode("\"" + str + "\"")); 540 }, 541 542 _formatregexp: function(re, elem) 543 { 544 var formatted = String(re.description).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1); 545 elem.appendChild(document.createTextNode(formatted)); 546 }, 547 548 _formatarray: function(arr, elem) 549 { 550 InjectedScriptAccess.getProperties(arr, false, this._printArray.bind(this, elem)); 551 }, 552 553 _printArray: function(elem, properties) 554 { 555 if (!properties) 556 return; 557 var elements = []; 558 for (var i = 0; i < properties.length; ++i) { 559 var name = properties[i].name; 560 if (name == parseInt(name)) 561 elements[name] = this._format(properties[i].value); 562 } 563 564 elem.appendChild(document.createTextNode("[")); 565 for (var i = 0; i < elements.length; ++i) { 566 var element = elements[i]; 567 if (element) 568 elem.appendChild(element); 569 else 570 elem.appendChild(document.createTextNode("undefined")) 571 if (i < elements.length - 1) 572 elem.appendChild(document.createTextNode(", ")); 573 } 574 elem.appendChild(document.createTextNode("]")); 504 _formatobject: function(obj, elem) 505 { 506 elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element); 575 507 }, 576 508 … … 589 521 elem.appendChild(treeOutline.element); 590 522 } 523 591 524 InjectedScriptAccess.pushNodeToFrontend(object, printNode); 592 525 }, 593 526 594 _formatobject: function(obj, elem) 595 { 596 elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element); 597 }, 598 599 _formaterror: function(obj, elem) 600 { 601 var messageElement = document.createElement("span"); 602 messageElement.className = "error-message"; 603 messageElement.textContent = obj.name + ": " + obj.message; 604 elem.appendChild(messageElement); 605 606 if (obj.sourceURL) { 607 var urlElement = document.createElement("a"); 608 urlElement.className = "webkit-html-resource-link"; 609 urlElement.href = obj.sourceURL; 610 urlElement.lineNumber = obj.line; 611 urlElement.preferredPanel = "scripts"; 612 613 if (obj.line > 0) 614 urlElement.textContent = WebInspector.displayNameForURL(obj.sourceURL) + ":" + obj.line; 527 _formatarray: function(arr, elem) 528 { 529 InjectedScriptAccess.getProperties(arr, false, this._printArray.bind(this, elem)); 530 }, 531 532 _printArray: function(elem, properties) 533 { 534 if (!properties) 535 return; 536 537 var elements = []; 538 for (var i = 0; i < properties.length; ++i) { 539 var name = properties[i].name; 540 if (name == parseInt(name)) 541 elements[name] = this._format(properties[i].value); 542 } 543 544 elem.appendChild(document.createTextNode("[")); 545 for (var i = 0; i < elements.length; ++i) { 546 var element = elements[i]; 547 if (element) 548 elem.appendChild(element); 615 549 else 616 urlElement.textContent = WebInspector.displayNameForURL(obj.sourceURL); 617 618 elem.appendChild(document.createTextNode(" (")); 619 elem.appendChild(urlElement); 620 elem.appendChild(document.createTextNode(")")); 621 } 550 elem.appendChild(document.createTextNode("undefined")) 551 if (i < elements.length - 1) 552 elem.appendChild(document.createTextNode(", ")); 553 } 554 elem.appendChild(document.createTextNode("]")); 622 555 } 623 556 } … … 674 607 _format: function(parameters) 675 608 { 609 // This node is used like a Builder. Values are contintually appended onto it. 676 610 var formattedResult = document.createElement("span"); 677 678 611 if (!parameters.length) 679 612 return formattedResult; 680 613 681 614 // Formatting code below assumes that parameters are all wrappers whereas frontend console 682 // API allows passing arbitrary values as messages (strings, number ts, etc.). Wrap them here.683 for (var i = 0; i < parameters.length; ++i) {615 // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here. 616 for (var i = 0; i < parameters.length; ++i) 684 617 if (typeof parameters[i] !== "object" && typeof parameters[i] !== "function") 685 618 parameters[i] = WebInspector.ObjectProxy.wrapPrimitiveValue(parameters[i]); 686 } 687 688 function formatForConsole(obj) 689 { 690 return WebInspector.console._format(obj); 691 } 692 693 function formatAsObjectForConsole(obj) 694 { 695 return WebInspector.console._format(obj, true); 696 } 697 698 if (Object.proxyType(parameters[0]) === "string") { 699 var formatters = {} 700 for (var i in String.standardFormatters) 701 formatters[i] = String.standardFormatters[i]; 702 703 // Firebug uses %o for formatting objects. 704 formatters.o = formatForConsole; 705 // Firebug allows both %i and %d for formatting integers. 706 formatters.i = formatters.d; 707 // Support %O to force object formating, instead of the type-based %o formatting. 708 formatters.O = formatAsObjectForConsole; 709 710 function append(a, b) 711 { 712 if (!(b instanceof Node)) 713 a.appendChild(WebInspector.linkifyStringAsFragment(b.toString())); 714 else 715 a.appendChild(b); 716 return a; 717 } 718 719 var result = String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append); 720 formattedResult = result.formattedResult; 619 620 // Multiple parameters with the first being a format string. Save unused substitutions. 621 if (parameters.length > 1 && Object.proxyType(parameters[0]) === "string") { 622 var result = this._formatWithSubstitutionString(parameters, formattedResult) 721 623 parameters = result.unusedSubstitutions; 722 624 if (parameters.length) … … 724 626 } 725 627 628 // Single parameter, or unused substitutions from above. 726 629 for (var i = 0; i < parameters.length; ++i) { 727 if (Object.proxyType(parameters[i]) === "string") 728 formattedResult.appendChild(WebInspector.linkifyStringAsFragment(parameters[i].description)); 729 else 730 formattedResult.appendChild(formatForConsole(parameters[i])); 731 630 this._formatIndividualValue(parameters[i], formattedResult); 732 631 if (i < parameters.length - 1) 733 632 formattedResult.appendChild(document.createTextNode(" ")); … … 735 634 736 635 return formattedResult; 636 }, 637 638 _formatWithSubstitutionString: function(parameters, formattedResult) 639 { 640 var formatters = {} 641 for (var i in String.standardFormatters) 642 formatters[i] = String.standardFormatters[i]; 643 644 function consoleFormatWrapper(force) 645 { 646 return function(obj) { 647 return WebInspector.console._format(obj, force); 648 }; 649 } 650 651 // Firebug uses %o for formatting objects. 652 formatters.o = consoleFormatWrapper(); 653 // Firebug allows both %i and %d for formatting integers. 654 formatters.i = formatters.d; 655 // Support %O to force object formating, instead of the type-based %o formatting. 656 formatters.O = consoleFormatWrapper(true); 657 658 function append(a, b) 659 { 660 if (!(b instanceof Node)) 661 a.appendChild(WebInspector.linkifyStringAsFragment(b.toString())); 662 else 663 a.appendChild(b); 664 return a; 665 } 666 667 // String.format does treat formattedResult like a Builder, result is an object. 668 return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append); 669 }, 670 671 _formatIndividualValue: function(param, formattedResult) 672 { 673 if (Object.proxyType(param) === "string") { 674 if (this.originatingCommand && this.level === WebInspector.ConsoleMessage.MessageLevel.Log) { 675 var quotedString = "\"" + param.description.replace(/"/g, "\\\"") + "\""; 676 formattedResult.appendChild(WebInspector.linkifyStringAsFragment(quotedString)); 677 } else 678 formattedResult.appendChild(WebInspector.linkifyStringAsFragment(param.description)); 679 } else 680 formattedResult.appendChild(WebInspector.console._format(param)); 737 681 }, 738 682 … … 984 928 var url = (exception ? result.sourceURL : null); 985 929 930 this.originatingCommand = originatingCommand; 931 986 932 WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, line, url, null, 1, message); 987 988 this.originatingCommand = originatingCommand;989 933 } 990 934 -
trunk/WebCore/inspector/front-end/InjectedScript.js
r51268 r51506 1217 1217 objectText = /.*/.exec(obj)[0].replace(/ +$/g, ""); 1218 1218 return objectText; 1219 case "regexp":1220 return String(obj).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);1221 1219 default: 1222 1220 return String(obj); -
trunk/WebCore/inspector/front-end/inspector.js
r51420 r51506 1465 1465 { 1466 1466 var container = document.createDocumentFragment(); 1467 var linkStringRegEx = new RegExp("(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}://|www\\.)[\\w$\\-_+*'=\\|/\\\\(){}[\\]%@&#~,:;.!?]{2,}[\\w$\\-_+*=\\|/\\\\({%@&#~]");1467 var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/; 1468 1468 1469 1469 while (string) { -
trunk/WebCore/inspector/front-end/utilities.js
r51339 r51506 179 179 if (!className) 180 180 return false; 181 // Test for the simple case before using a RegExp.181 // Test for the simple case 182 182 if (this.className === className) 183 183 return true; … … 351 351 String.prototype.trimURL = function(baseURLDomain) 352 352 { 353 var result = this.replace( new RegExp("^http[s]?:\/\/", "i"), "");353 var result = this.replace(/^https?:\/\//i, ""); 354 354 if (baseURLDomain) 355 355 result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), "");
Note: See TracChangeset
for help on using the changeset viewer.