Changeset 64142 in webkit
- Timestamp:
- Jul 27, 2010 11:14:31 AM (14 years ago)
- Location:
- trunk/WebCore
- Files:
-
- 11 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/WebCore/ChangeLog
r64141 r64142 1 2010-07-27 Victor Wang <victorw@chromium.org> 2 3 Unreviewed, rolling out r64104. 4 http://trac.webkit.org/changeset/64104 5 https://bugs.webkit.org/show_bug.cgi?id=40768 6 7 The patch causes chromium webkit socket laytest crashes on 8 windows randomly 9 10 * inspector/InspectorController.cpp: 11 (WebCore::InspectorController::addResource): 12 (WebCore::InspectorController::removeResource): 13 * inspector/InspectorController.h: 14 * inspector/InspectorResource.cpp: 15 (WebCore::InspectorResource::InspectorResource): 16 (WebCore::InspectorResource::updateScriptObject): 17 (WebCore::InspectorResource::cachedResource): 18 (WebCore::InspectorResource::type): 19 (WebCore::InspectorResource::resourceData): 20 * inspector/InspectorResource.h: 21 (WebCore::InspectorResource::): 22 (WebCore::InspectorResource::create): 23 * inspector/front-end/Resource.js: 24 (WebInspector.Resource.Type.toString): 25 (WebInspector.Resource.prototype.set type): 26 (WebInspector.Resource.prototype._mimeTypeIsConsistentWithType): 27 * inspector/front-end/ResourceView.js: 28 (WebInspector.ResourceView.prototype._refreshRequestHeaders): 29 (WebInspector.ResourceView.prototype._refreshResponseHeaders): 30 (WebInspector.ResourceView.prototype._refreshHeaders): 31 * inspector/front-end/inspector.css: 32 (.resources-category-scripts, .resources-category-xhr, .resources-category-fonts, .resources-category-other): 33 * inspector/front-end/inspector.js: 34 (WebInspector.loaded): 35 (WebInspector.updateResource): 36 * websockets/WebSocketChannel.cpp: 37 (WebCore::WebSocketChannel::WebSocketChannel): 38 (WebCore::WebSocketChannel::disconnect): 39 (WebCore::WebSocketChannel::didOpen): 40 (WebCore::WebSocketChannel::didClose): 41 (WebCore::WebSocketChannel::processBuffer): 42 * websockets/WebSocketChannel.h: 43 1 44 2010-07-27 Andrei Popescu <andreip@google.com> 2 45 -
trunk/WebCore/inspector/InspectorController.cpp
r64104 r64142 827 827 828 828 Frame* frame = resource->frame(); 829 if (!frame)830 return;831 829 ResourcesMap* resourceMap = m_frameResources.get(frame); 832 830 if (resourceMap) … … 847 845 848 846 Frame* frame = resource->frame(); 849 if (!frame)850 return;851 847 ResourcesMap* resourceMap = m_frameResources.get(frame); 852 848 if (!resourceMap) { … … 1491 1487 } 1492 1488 #endif 1493 1494 #if ENABLE(WEB_SOCKETS)1495 void InspectorController::didCreateWebSocket(unsigned long identifier, const KURL& requestURL, const KURL& documentURL)1496 {1497 if (!enabled())1498 return;1499 ASSERT(m_inspectedPage);1500 1501 RefPtr<InspectorResource> resource = InspectorResource::createWebSocket(identifier, requestURL, documentURL);1502 addResource(resource.get());1503 1504 if (m_frontend)1505 resource->updateScriptObject(m_frontend.get());1506 }1507 1508 void InspectorController::willSendWebSocketHandshakeRequest(unsigned long identifier, const WebSocketHandshakeRequest& request)1509 {1510 RefPtr<InspectorResource> resource = getTrackedResource(identifier);1511 if (!resource)1512 return;1513 resource->startTiming();1514 resource->updateWebSocketRequest(request);1515 if (m_frontend)1516 resource->updateScriptObject(m_frontend.get());1517 }1518 1519 void InspectorController::didReceiveWebSocketHandshakeResponse(unsigned long identifier, const WebSocketHandshakeResponse& response)1520 {1521 RefPtr<InspectorResource> resource = getTrackedResource(identifier);1522 if (!resource)1523 return;1524 // Calling resource->markResponseReceivedTime() here makes resources bar chart confusing, because1525 // we cannot apply the "latency + download" model of regular resources to WebSocket connections.1526 // FIXME: Design a new UI for bar charts of WebSocket resources, and record timing here.1527 resource->updateWebSocketResponse(response);1528 if (m_frontend)1529 resource->updateScriptObject(m_frontend.get());1530 }1531 1532 void InspectorController::didCloseWebSocket(unsigned long identifier)1533 {1534 RefPtr<InspectorResource> resource = getTrackedResource(identifier);1535 if (!resource)1536 return;1537 1538 resource->endTiming();1539 if (m_frontend)1540 resource->updateScriptObject(m_frontend.get());1541 }1542 #endif // ENABLE(WEB_SOCKETS)1543 1489 1544 1490 #if ENABLE(JAVASCRIPT_DEBUGGER) -
trunk/WebCore/inspector/InspectorController.h
r64104 r64142 93 93 #endif 94 94 95 #if ENABLE(WEB_SOCKETS)96 class WebSocketHandshakeRequest;97 class WebSocketHandshakeResponse;98 #endif99 100 95 class InspectorController 101 96 #if ENABLE(JAVASCRIPT_DEBUGGER) … … 229 224 void removeDOMStorageItem(long callId, long storageId, const String& key); 230 225 #endif 231 #if ENABLE(WEB_SOCKETS)232 void didCreateWebSocket(unsigned long identifier, const KURL& requestURL, const KURL& documentURL);233 void willSendWebSocketHandshakeRequest(unsigned long identifier, const WebSocketHandshakeRequest&);234 void didReceiveWebSocketHandshakeResponse(unsigned long identifier, const WebSocketHandshakeResponse&);235 void didCloseWebSocket(unsigned long identifier);236 #endif237 226 238 227 const ResourcesMap& resources() const { return m_resources; } -
trunk/WebCore/inspector/InspectorResource.cpp
r64104 r64142 43 43 #include "ResourceRequest.h" 44 44 #include "ResourceResponse.h" 45 #include "StringBuffer.h"46 45 #include "TextEncoding.h" 47 46 #include "ScriptObject.h" 48 #include "WebSocketHandshakeRequest.h"49 #include "WebSocketHandshakeResponse.h"50 51 #include <wtf/Assertions.h>52 47 53 48 namespace WebCore { 54 55 // Create human-readable binary representation, like "01:23:45:67:89:AB:CD:EF".56 static String createReadableStringFromBinary(const unsigned char* value, size_t length)57 {58 ASSERT(length > 0);59 static const char hexDigits[17] = "0123456789ABCDEF";60 size_t bufferSize = length * 3 - 1;61 StringBuffer buffer(bufferSize);62 size_t index = 0;63 for (size_t i = 0; i < length; ++i) {64 if (i > 0)65 buffer[index++] = ':';66 buffer[index++] = hexDigits[value[i] >> 4];67 buffer[index++] = hexDigits[value[i] & 0xF];68 }69 ASSERT(index == bufferSize);70 return String::adopt(buffer);71 }72 49 73 50 InspectorResource::InspectorResource(unsigned long identifier, DocumentLoader* loader, const KURL& requestURL) 74 51 : m_identifier(identifier) 75 52 , m_loader(loader) 76 , m_frame(loader ? loader->frame() : 0)53 , m_frame(loader->frame()) 77 54 , m_requestURL(requestURL) 78 55 , m_expectedContentLength(0) … … 90 67 , m_connectionReused(false) 91 68 , m_isMainResource(false) 92 #if ENABLE(WEB_SOCKETS)93 , m_isWebSocket(false)94 #endif95 69 { 96 70 } … … 115 89 } 116 90 117 PassRefPtr<InspectorResource> InspectorResource::create(unsigned long identifier, DocumentLoader* loader, const KURL& requestURL)118 {119 ASSERT(loader);120 return adoptRef(new InspectorResource(identifier, loader, requestURL));121 }122 123 91 PassRefPtr<InspectorResource> InspectorResource::createCached(unsigned long identifier, DocumentLoader* loader, const CachedResource* cachedResource) 124 92 { … … 139 107 return resource; 140 108 } 141 142 #if ENABLE(WEB_SOCKETS)143 PassRefPtr<InspectorResource> InspectorResource::createWebSocket(unsigned long identifier, const KURL& requestURL, const KURL& documentURL)144 {145 RefPtr<InspectorResource> resource = adoptRef(new InspectorResource(identifier, 0, requestURL));146 resource->markWebSocket();147 resource->m_documentURL = documentURL;148 return resource.release();149 }150 #endif151 109 152 110 void InspectorResource::updateRequest(const ResourceRequest& request) … … 189 147 } 190 148 191 #if ENABLE(WEB_SOCKETS)192 void InspectorResource::updateWebSocketRequest(const WebSocketHandshakeRequest& request)193 {194 m_requestHeaderFields = request.headerFields();195 m_requestMethod = "GET"; // Currently we always use "GET" to request handshake.196 m_webSocketRequestKey3.set(new WebSocketHandshakeRequest::Key3(request.key3()));197 m_changes.set(RequestChange);198 m_changes.set(TypeChange);199 }200 201 void InspectorResource::updateWebSocketResponse(const WebSocketHandshakeResponse& response)202 {203 m_responseStatusCode = response.statusCode();204 m_responseStatusText = response.statusText();205 m_responseHeaderFields = response.headerFields();206 m_webSocketChallengeResponse.set(new WebSocketHandshakeResponse::ChallengeResponse(response.challengeResponse()));207 m_changes.set(ResponseChange);208 m_changes.set(TypeChange);209 }210 #endif // ENABLE(WEB_SOCKETS)211 212 149 static void populateHeadersObject(ScriptObject* object, const HTTPHeaderMap& headers) 213 150 { … … 225 162 ScriptObject jsonObject = frontend->newScriptObject(); 226 163 if (m_changes.hasChange(RequestChange)) { 227 if (m_frame)228 m_documentURL = m_frame->document()->url();229 230 164 jsonObject.set("url", m_requestURL.string()); 231 jsonObject.set("documentURL", m_ documentURL.string());165 jsonObject.set("documentURL", m_frame->document()->url().string()); 232 166 jsonObject.set("host", m_requestURL.host()); 233 167 jsonObject.set("path", m_requestURL.path()); … … 240 174 jsonObject.set("requestFormData", m_requestFormData); 241 175 jsonObject.set("didRequestChange", true); 242 #if ENABLE(WEB_SOCKETS)243 if (m_webSocketRequestKey3)244 jsonObject.set("webSocketRequestKey3", createReadableStringFromBinary(m_webSocketRequestKey3->value, sizeof(m_webSocketRequestKey3->value)));245 #endif246 176 } 247 177 … … 260 190 if (m_loadTiming && !m_cached) 261 191 jsonObject.set("timing", buildObjectForTiming(frontend, m_loadTiming.get())); 262 #if ENABLE(WEB_SOCKETS)263 if (m_webSocketChallengeResponse)264 jsonObject.set("webSocketChallengeResponse", createReadableStringFromBinary(m_webSocketChallengeResponse->value, sizeof(m_webSocketChallengeResponse->value)));265 #endif266 192 jsonObject.set("didResponseChange", true); 267 193 } … … 322 248 // but Inspector will already try to fetch data that is only available via CachedResource (and it won't update once the resource is added, 323 249 // because m_changes will not have the appropriate bits set). 324 if (!m_frame)325 return 0;326 250 const String& url = m_requestURL.string(); 327 251 CachedResource* cachedResource = m_frame->document()->docLoader()->cachedResource(url); … … 360 284 return m_overrideContentType; 361 285 362 #if ENABLE(WEB_SOCKETS)363 if (m_isWebSocket)364 return WebSocket;365 #endif366 367 ASSERT(m_loader);368 286 if (m_requestURL == m_loader->requestURL()) { 369 287 InspectorResource::Type resourceType = cachedResourceType(); … … 405 323 PassRefPtr<SharedBuffer> InspectorResource::resourceData(String* textEncodingName) const 406 324 { 407 if (m_ loader && m_requestURL == m_loader->requestURL()) {325 if (m_requestURL == m_loader->requestURL()) { 408 326 *textEncodingName = m_frame->document()->inputEncoding(); 409 327 return m_loader->mainResourceData(); -
trunk/WebCore/inspector/InspectorResource.h
r64104 r64142 37 37 #include "ScriptState.h" 38 38 #include "ScriptString.h" 39 #include "WebSocketHandshakeRequest.h"40 #include "WebSocketHandshakeResponse.h"41 39 42 40 #include <wtf/CurrentTime.h> … … 56 54 class ResourceResponse; 57 55 58 #if ENABLE(WEB_SOCKETS)59 class WebSocketHandshakeRequest;60 class WebSocketHandshakeResponse;61 #endif62 63 56 class InspectorResource : public RefCounted<InspectorResource> { 64 57 public: … … 73 66 XHR, 74 67 Media, 75 WebSocket,76 68 Other 77 69 }; 78 70 79 static PassRefPtr<InspectorResource> create(unsigned long identifier, DocumentLoader* loader, const KURL& requestURL); 71 static PassRefPtr<InspectorResource> create(unsigned long identifier, DocumentLoader* loader, const KURL& requestURL) 72 { 73 return adoptRef(new InspectorResource(identifier, loader, requestURL)); 74 } 80 75 81 76 static PassRefPtr<InspectorResource> createCached(unsigned long identifier, DocumentLoader*, const CachedResource*); 82 83 #if ENABLE(WEB_SOCKETS)84 // WebSocket resource doesn't have its loader. For WebSocket resources, m_loader and m_frame will become null.85 static PassRefPtr<InspectorResource> createWebSocket(unsigned long identifier, const KURL& requestURL, const KURL& documentURL);86 #endif87 77 88 78 ~InspectorResource(); … … 94 84 void updateRequest(const ResourceRequest&); 95 85 void updateResponse(const ResourceResponse&); 96 97 #if ENABLE(WEB_SOCKETS)98 void updateWebSocketRequest(const WebSocketHandshakeRequest&);99 void updateWebSocketResponse(const WebSocketHandshakeResponse&);100 #endif101 86 102 87 void setOverrideContent(const ScriptString& data, Type); … … 169 154 ScriptObject buildObjectForTiming(InspectorFrontend*, ResourceLoadTiming*); 170 155 171 #if ENABLE(WEB_SOCKETS)172 void markWebSocket() { m_isWebSocket = true; }173 #endif174 175 156 unsigned long m_identifier; 176 157 RefPtr<DocumentLoader> m_loader; 177 158 RefPtr<Frame> m_frame; 178 159 KURL m_requestURL; 179 KURL m_documentURL;180 160 HTTPHeaderMap m_requestHeaderFields; 181 161 HTTPHeaderMap m_responseHeaderFields; … … 204 184 String m_requestFormData; 205 185 Vector<RefPtr<InspectorResource> > m_redirects; 206 207 #if ENABLE(WEB_SOCKETS)208 bool m_isWebSocket;209 210 // The following fields are not used for resources other than WebSocket.211 // We allocate them dynamically to reduce memory consumption for regular resources.212 OwnPtr<WebSocketHandshakeRequest::Key3> m_webSocketRequestKey3;213 OwnPtr<WebSocketHandshakeResponse::ChallengeResponse> m_webSocketChallengeResponse;214 #endif215 186 }; 216 187 -
trunk/WebCore/inspector/front-end/Resource.js
r64104 r64142 46 46 XHR: 5, 47 47 Media: 6, 48 WebSocket: 7, 49 Other: 8, 48 Other: 7, 50 49 51 50 isTextType: function(type) … … 69 68 case this.XHR: 70 69 return WebInspector.UIString("XHR"); 71 case this.WebSocket:72 return WebInspector.UIString("WebSocket");73 70 case this.Other: 74 71 default: … … 367 364 this.category = WebInspector.resourceCategories.xhr; 368 365 break; 369 case WebInspector.Resource.Type.WebSocket:370 this.category = WebInspector.resourceCategories.websocket;371 break;372 366 case WebInspector.Resource.Type.Other: 373 367 default: … … 582 576 if (typeof this.type === "undefined" 583 577 || this.type === WebInspector.Resource.Type.Other 584 || this.type === WebInspector.Resource.Type.XHR 585 || this.type === WebInspector.Resource.Type.WebSocket) 578 || this.type === WebInspector.Resource.Type.XHR) 586 579 return true; 587 580 -
trunk/WebCore/inspector/front-end/ResourceView.js
r64104 r64142 279 279 _refreshRequestHeaders: function() 280 280 { 281 var additionalRow = null; 282 if (typeof this.resource.webSocketRequestKey3 !== "undefined") 283 additionalRow = {header: "(Key3)", value: this.resource.webSocketRequestKey3}; 284 this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, additionalRow, this.requestHeadersTreeElement); 281 this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, this.requestHeadersTreeElement); 285 282 this._refreshFormData(); 286 283 }, … … 288 285 _refreshResponseHeaders: function() 289 286 { 290 var additionalRow = null; 291 if (typeof this.resource.webSocketChallengeResponse !== "undefined") 292 additionalRow = {header: "(Challenge Response)", value: this.resource.webSocketChallengeResponse}; 293 this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, additionalRow, this.responseHeadersTreeElement); 287 this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, this.responseHeadersTreeElement); 294 288 }, 295 289 … … 322 316 }, 323 317 324 _refreshHeaders: function(title, headers, additionalRow,headersTreeElement)318 _refreshHeaders: function(title, headers, headersTreeElement) 325 319 { 326 320 headersTreeElement.removeChildren(); … … 339 333 headersTreeElement.appendChild(headerTreeElement); 340 334 } 341 342 if (additionalRow) {343 var title = "<div class=\"header-name\">" + additionalRow.header.escapeHTML() + ":</div>";344 title += "<div class=\"header-value source-code\">" + additionalRow.value.escapeHTML() + "</div>"345 346 var headerTreeElement = new TreeElement(title, null, false);347 headerTreeElement.selectable = false;348 headersTreeElement.appendChild(headerTreeElement);349 }350 335 } 351 336 } -
trunk/WebCore/inspector/front-end/inspector.css
r64104 r64142 2820 2820 2821 2821 .resources-category-documents, .resources-category-stylesheets, .resources-category-images, 2822 .resources-category-scripts, .resources-category-xhr, .resources-category-fonts, 2823 .resources-category-websockets, .resources-category-other { 2822 .resources-category-scripts, .resources-category-xhr, .resources-category-fonts, .resources-category-other { 2824 2823 display: none; 2825 2824 } … … 2831 2830 .filter-all .resources-category-xhr, .filter-xhr .resources-category-xhr, 2832 2831 .filter-all .resources-category-fonts, .filter-fonts .resources-category-fonts, 2833 .filter-all .resources-category-websockets, .filter-websockets .resources-category-websockets,2834 2832 .filter-all .resources-category-other, .filter-other .resources-category-other, 2835 2833 .resource-sidebar-tree-item.selected { … … 2905 2903 .resources-category-xhr.resource-cached .resources-graph-bar { 2906 2904 -webkit-border-image: url(Images/timelineHollowPillYellow.png) 6 7 6 7; 2907 }2908 2909 /* FIXME: Create bar images for WebSocket. */2910 .resources-category-websockets .resources-graph-bar {2911 -webkit-border-image: url(Images/timelinePillGray.png) 6 7 6 7;2912 }2913 2914 .resources-category-websockets.resource-cached .resources-graph-bar {2915 -webkit-border-image: url(Images/timelineHollowPillGray.png) 6 7 6 7;2916 2905 } 2917 2906 -
trunk/WebCore/inspector/front-end/inspector.js
r64104 r64142 465 465 xhr: new WebInspector.ResourceCategory("xhr", WebInspector.UIString("XHR"), "rgb(231,231,10)"), 466 466 fonts: new WebInspector.ResourceCategory("fonts", WebInspector.UIString("Fonts"), "rgb(255,82,62)"), 467 websocket: new WebInspector.ResourceCategory("websockets", WebInspector.UIString("WebSocket"), "rgb(186,186,186)"), // FIXME: Decide the color.468 467 other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)") 469 468 }; … … 1165 1164 resource.requestFormData = payload.requestFormData; 1166 1165 resource.documentURL = payload.documentURL; 1167 if (typeof payload.webSocketRequestKey3 !== "undefined")1168 resource.webSocketRequestKey3 = payload.webSocketRequestKey3;1169 1166 1170 1167 if (resource.mainResource) … … 1191 1188 resource.timing = payload.timing; 1192 1189 resource.cached = payload.cached; 1193 if (typeof payload.webSocketChallengeResponse !== "undefined")1194 resource.webSocketChallengeResponse = payload.webSocketChallengeResponse;1195 1190 } 1196 1191 -
trunk/WebCore/websockets/WebSocketChannel.cpp
r64104 r64142 37 37 #include "CookieJar.h" 38 38 #include "Document.h" 39 #include "InspectorController.h"40 39 #include "Logging.h" 41 #include "Page.h"42 40 #include "PlatformString.h" 43 #include "ProgressTracker.h"44 41 #include "ScriptExecutionContext.h" 45 42 #include "SocketStreamError.h" … … 66 63 , m_closed(false) 67 64 , m_unhandledBufferedAmount(0) 68 #if ENABLE(INSPECTOR) 69 , m_identifier(0) 70 #endif 71 { 72 #if ENABLE(INSPECTOR) 73 if (InspectorController* controller = m_context->inspectorController()) 74 controller->didCreateWebSocket(identifier(), url, m_context->url()); 75 #endif 65 { 76 66 } 77 67 … … 122 112 { 123 113 LOG(Network, "WebSocketChannel %p disconnect", this); 124 #if ENABLE(INSPECTOR)125 if (m_context)126 if (InspectorController* controller = m_context->inspectorController())127 controller->didCloseWebSocket(identifier());128 #endif129 114 m_handshake.clearScriptExecutionContext(); 130 115 m_client = 0; … … 152 137 if (!m_context) 153 138 return; 154 #if ENABLE(INSPECTOR)155 if (InspectorController* controller = m_context->inspectorController())156 controller->willSendWebSocketHandshakeRequest(identifier(), m_handshake.clientHandshakeRequest());157 #endif158 139 const CString& handshakeMessage = m_handshake.clientHandshakeMessage(); 159 140 if (!handle->send(handshakeMessage.data(), handshakeMessage.length())) { … … 166 147 { 167 148 LOG(Network, "WebSocketChannel %p didClose", this); 168 #if ENABLE(INSPECTOR)169 if (m_context)170 if (InspectorController* controller = m_context->inspectorController())171 controller->didCloseWebSocket(identifier());172 #endif173 149 ASSERT_UNUSED(handle, handle == m_handle || !m_handle); 174 150 m_closed = true; … … 262 238 return false; 263 239 if (m_handshake.mode() == WebSocketHandshake::Connected) { 264 #if ENABLE(INSPECTOR)265 if (InspectorController* controller = m_context->inspectorController())266 controller->didReceiveWebSocketHandshakeResponse(identifier(), m_handshake.serverHandshakeResponse());267 #endif268 240 if (!m_handshake.serverSetCookie().isEmpty()) { 269 241 if (m_context->isDocument()) { … … 357 329 } 358 330 359 #if ENABLE(INSPECTOR)360 unsigned long WebSocketChannel::identifier()361 {362 if (m_identifier)363 return m_identifier;364 365 if (InspectorController* controller = m_context->inspectorController())366 if (Page* page = controller->inspectedPage())367 m_identifier = page->progress()->createUniqueIdentifier();368 369 ASSERT(m_identifier);370 return m_identifier;371 }372 #endif // ENABLE(INSPECTOR)373 374 331 } // namespace WebCore 375 332 -
trunk/WebCore/websockets/WebSocketChannel.h
r64104 r64142 85 85 void resumeTimerFired(Timer<WebSocketChannel>* timer); 86 86 87 #if ENABLE(INSPECTOR)88 unsigned long identifier();89 #endif90 91 87 ScriptExecutionContext* m_context; 92 88 WebSocketChannelClient* m_client; … … 100 96 bool m_closed; 101 97 unsigned long m_unhandledBufferedAmount; 102 103 #if ENABLE(INSPECTOR)104 unsigned long m_identifier;105 #endif106 98 }; 107 99
Note: See TracChangeset
for help on using the changeset viewer.