40badcb5442ea05158db7ce1f5632e65daba380f
[WebKit-https.git] / Source / WebCore / inspector / front-end / RequestHeadersView.js
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) IBM Corp. 2009  All rights reserved.
4  * Copyright (C) 2010 Google Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @constructor
33  * @extends {WebInspector.View}
34  * @param {WebInspector.NetworkRequest} request
35  */
36 WebInspector.RequestHeadersView = function(request)
37 {
38     WebInspector.View.call(this);
39     this.registerRequiredCSS("resourceView.css");
40     this.element.addStyleClass("resource-headers-view");
41
42     this._request = request;
43
44     this._headersListElement = document.createElement("ol");
45     this._headersListElement.className = "outline-disclosure";
46     this.element.appendChild(this._headersListElement);
47
48     this._headersTreeOutline = new TreeOutline(this._headersListElement);
49     this._headersTreeOutline.expandTreeElementsWhenArrowing = true;
50
51     this._urlTreeElement = new TreeElement("", null, false);
52     this._urlTreeElement.selectable = false;
53     this._headersTreeOutline.appendChild(this._urlTreeElement);
54
55     this._requestMethodTreeElement = new TreeElement("", null, false);
56     this._requestMethodTreeElement.selectable = false;
57     this._headersTreeOutline.appendChild(this._requestMethodTreeElement);
58
59     this._statusCodeTreeElement = new TreeElement("", null, false);
60     this._statusCodeTreeElement.selectable = false;
61     this._headersTreeOutline.appendChild(this._statusCodeTreeElement);
62
63     this._requestHeadersTreeElement = new TreeElement("", null, true);
64     this._requestHeadersTreeElement.expanded = true;
65     this._requestHeadersTreeElement.selectable = false;
66     this._headersTreeOutline.appendChild(this._requestHeadersTreeElement);
67
68     this._decodeRequestParameters = true;
69
70     this._showRequestHeadersText = false;
71     this._showResponseHeadersText = false;
72
73     this._queryStringTreeElement = new TreeElement("", null, true);
74     this._queryStringTreeElement.expanded = true;
75     this._queryStringTreeElement.selectable = false;
76     this._queryStringTreeElement.hidden = true;
77     this._headersTreeOutline.appendChild(this._queryStringTreeElement);
78
79     this._urlFragmentTreeElement = new TreeElement("", null, true);
80     this._urlFragmentTreeElement.expanded = true;
81     this._urlFragmentTreeElement.selectable = false;
82     this._urlFragmentTreeElement.hidden = true;
83     this._headersTreeOutline.appendChild(this._urlFragmentTreeElement);
84
85     this._formDataTreeElement = new TreeElement("", null, true);
86     this._formDataTreeElement.expanded = true;
87     this._formDataTreeElement.selectable = false;
88     this._formDataTreeElement.hidden = true;
89     this._headersTreeOutline.appendChild(this._formDataTreeElement);
90
91     this._requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true);
92     this._requestPayloadTreeElement.expanded = true;
93     this._requestPayloadTreeElement.selectable = false;
94     this._requestPayloadTreeElement.hidden = true;
95     this._headersTreeOutline.appendChild(this._requestPayloadTreeElement);
96
97     this._responseHeadersTreeElement = new TreeElement("", null, true);
98     this._responseHeadersTreeElement.expanded = true;
99     this._responseHeadersTreeElement.selectable = false;
100     this._headersTreeOutline.appendChild(this._responseHeadersTreeElement);
101
102     request.addEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
103     request.addEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
104     request.addEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
105
106     this._refreshURL();
107     this._refreshQueryString();
108     this._refreshUrlFragment();
109     this._refreshRequestHeaders();
110     this._refreshResponseHeaders();
111     this._refreshHTTPInformation();
112 }
113
114 WebInspector.RequestHeadersView.prototype = {
115     /**
116      * @param {string} name
117      * @param {string} value
118      */
119     _formatHeader: function(name, value)
120     {
121         var fragment = document.createDocumentFragment();
122         fragment.createChild("div", "header-name").textContent = name + ":";
123         fragment.createChild("div", "header-value source-code").textContent = value;
124
125         return fragment;
126     },
127
128     /**
129      * @param {string} value
130      * @param {string} className
131      * @param {boolean} decodeParameters
132      */
133     _formatParameter: function(value, className, decodeParameters)
134     {
135         var errorDecoding = false;
136
137         if (decodeParameters) {
138             value = value.replace(/\+/g, " ");
139             if (value.indexOf("%") >= 0) {
140                 try {
141                     value = decodeURIComponent(value);
142                 } catch(e) {
143                     errorDecoding = true;
144                 }
145             }
146         }
147         var div = document.createElement("div");
148         div.className = className;
149         if (errorDecoding)
150             div.createChild("span", "error-message").textContent = WebInspector.UIString("(unable to decode value)");
151         else
152             div.textContent = value;
153         return div;
154     },
155
156     _refreshURL: function()
157     {
158         this._urlTreeElement.title = this._formatHeader(WebInspector.UIString("Request URL"), this._request.url);
159     },
160
161     _refreshQueryString: function()
162     {
163         var queryString = this._request.queryString();
164         var queryParameters = this._request.queryParameters;
165         this._queryStringTreeElement.hidden = !queryParameters;
166         if (queryParameters)
167             this._refreshParams(WebInspector.UIString("Query String Parameters"), queryParameters, queryString, this._queryStringTreeElement);
168     },
169
170     _refreshUrlFragment: function()
171     {
172         var urlFragment = this._request.parsedURL.fragment;
173         this._urlFragmentTreeElement.hidden = !urlFragment;
174
175         if (!urlFragment)
176             return;
177
178         var sectionTitle = WebInspector.UIString("URL fragment");
179
180         this._urlFragmentTreeElement.removeChildren();
181         this._urlFragmentTreeElement.listItemElement.removeChildren();
182         this._urlFragmentTreeElement.listItemElement.appendChild(document.createTextNode(sectionTitle));
183
184         var fragmentTreeElement = new TreeElement(null, null, false);
185         fragmentTreeElement.title = this._formatHeader("#", urlFragment);
186         fragmentTreeElement.selectable = false;
187         this._urlFragmentTreeElement.appendChild(fragmentTreeElement);
188     },
189
190     _refreshFormData: function()
191     {
192         this._formDataTreeElement.hidden = true;
193         this._requestPayloadTreeElement.hidden = true;
194
195         var formData = this._request.requestFormData;
196         if (!formData)
197             return;
198
199         var formParameters = this._request.formParameters;
200         if (formParameters) {
201             this._formDataTreeElement.hidden = false;
202             this._refreshParams(WebInspector.UIString("Form Data"), formParameters, formData, this._formDataTreeElement);
203         } else {
204             this._requestPayloadTreeElement.hidden = false;
205             this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, formData)
206         }
207     },
208
209     _populateTreeElementWithSourceText: function(treeElement, sourceText)
210     {
211         treeElement.removeChildren();
212
213         var sourceTreeElement = new TreeElement(null, null, false);
214         sourceTreeElement.selectable = false;
215         treeElement.appendChild(sourceTreeElement);
216
217         var sourceTextElement = document.createElement("span");
218         sourceTextElement.addStyleClass("header-value");
219         sourceTextElement.addStyleClass("source-code");
220         sourceTextElement.textContent = String(sourceText).trim();
221         sourceTreeElement.listItemElement.appendChild(sourceTextElement);
222     },
223
224     _refreshParams: function(title, params, sourceText, paramsTreeElement)
225     {
226         paramsTreeElement.removeChildren();
227
228         paramsTreeElement.listItemElement.removeChildren();
229         paramsTreeElement.listItemElement.appendChild(document.createTextNode(title));
230
231         var headerCount = document.createElement("span");
232         headerCount.addStyleClass("header-count");
233         headerCount.textContent = WebInspector.UIString(" (%d)", params.length);
234         paramsTreeElement.listItemElement.appendChild(headerCount);
235
236         function toggleViewSource()
237         {
238             paramsTreeElement._viewSource = !paramsTreeElement._viewSource;
239             this._refreshParams(title, params, sourceText, paramsTreeElement);
240         }
241
242         var viewSourceToggleTitle = paramsTreeElement._viewSource ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source");
243         var viewSourceToggleButton = this._createToggleButton(viewSourceToggleTitle);
244         viewSourceToggleButton.addEventListener("click", toggleViewSource.bind(this));
245         paramsTreeElement.listItemElement.appendChild(viewSourceToggleButton);
246         
247         if (paramsTreeElement._viewSource) {
248             this._populateTreeElementWithSourceText(paramsTreeElement, sourceText);
249             return;
250         }
251
252         var toggleTitle = this._decodeRequestParameters ? WebInspector.UIString("view URL encoded") : WebInspector.UIString("view decoded");
253         var toggleButton = this._createToggleButton(toggleTitle);
254         toggleButton.addEventListener("click", this._toggleURLDecoding.bind(this));
255         paramsTreeElement.listItemElement.appendChild(toggleButton);
256
257         for (var i = 0; i < params.length; ++i) {
258             var paramNameValue = document.createDocumentFragment();
259             var name = this._formatParameter(params[i].name + ":", "header-name", this._decodeRequestParameters);
260             var value = this._formatParameter(params[i].value, "header-value source-code", this._decodeRequestParameters);
261             paramNameValue.appendChild(name);
262             paramNameValue.appendChild(value);
263
264             var parmTreeElement = new TreeElement(paramNameValue, null, false);
265             parmTreeElement.selectable = false;
266             paramsTreeElement.appendChild(parmTreeElement);
267         }
268     },
269
270     _toggleURLDecoding: function(event)
271     {
272         this._decodeRequestParameters = !this._decodeRequestParameters;
273         this._refreshQueryString();
274         this._refreshFormData();
275     },
276
277     _getHeaderValue: function(headers, key)
278     {
279         var lowerKey = key.toLowerCase();
280         for (var testKey in headers) {
281             if (testKey.toLowerCase() === lowerKey)
282                 return headers[testKey];
283         }
284     },
285
286     _refreshRequestHeaders: function()
287     {
288         var additionalRow = null;
289         if (typeof this._request.webSocketRequestKey3 !== "undefined")
290             additionalRow = {name: "(Key3)", value: this._request.webSocketRequestKey3};
291         if (this._showRequestHeadersText)
292             this._refreshHeadersText(WebInspector.UIString("Request Headers"), this._request.sortedRequestHeaders, this._request.requestHeadersText, this._requestHeadersTreeElement);
293         else
294             this._refreshHeaders(WebInspector.UIString("Request Headers"), this._request.sortedRequestHeaders, additionalRow, this._requestHeadersTreeElement);
295
296         if (this._request.requestHeadersText) {
297             var toggleButton = this._createHeadersToggleButton(this._showRequestHeadersText);
298             toggleButton.addEventListener("click", this._toggleRequestHeadersText.bind(this));
299             this._requestHeadersTreeElement.listItemElement.appendChild(toggleButton);
300         }
301
302         this._refreshFormData();
303     },
304
305     _refreshResponseHeaders: function()
306     {
307         var additionalRow = null;
308         if (typeof this._request.webSocketChallengeResponse !== "undefined")
309             additionalRow = {name: "(Challenge Response)", value: this._request.webSocketChallengeResponse};
310         if (this._showResponseHeadersText)
311             this._refreshHeadersText(WebInspector.UIString("Response Headers"), this._request.sortedResponseHeaders, this._request.responseHeadersText, this._responseHeadersTreeElement);
312         else
313             this._refreshHeaders(WebInspector.UIString("Response Headers"), this._request.sortedResponseHeaders, additionalRow, this._responseHeadersTreeElement);
314
315         if (this._request.responseHeadersText) {
316             var toggleButton = this._createHeadersToggleButton(this._showResponseHeadersText);
317             toggleButton.addEventListener("click", this._toggleResponseHeadersText.bind(this));
318             this._responseHeadersTreeElement.listItemElement.appendChild(toggleButton);
319         }
320     },
321
322     _refreshHTTPInformation: function()
323     {
324         var requestMethodElement = this._requestMethodTreeElement;
325         requestMethodElement.hidden = !this._request.statusCode;
326         var statusCodeElement = this._statusCodeTreeElement;
327         statusCodeElement.hidden = !this._request.statusCode;
328
329         if (this._request.statusCode) {
330             var statusImageSource = "";
331             if (this._request.statusCode < 300 || this._request.statusCode === 304)
332                 statusImageSource = "Images/successGreenDot.png";
333             else if (this._request.statusCode < 400)
334                 statusImageSource = "Images/warningOrangeDot.png";
335             else
336                 statusImageSource = "Images/errorRedDot.png";
337
338             requestMethodElement.title = this._formatHeader(WebInspector.UIString("Request Method"), this._request.requestMethod);
339
340             var statusCodeFragment = document.createDocumentFragment();
341             statusCodeFragment.createChild("div", "header-name").textContent = WebInspector.UIString("Status Code") + ":";
342
343             var statusCodeImage = statusCodeFragment.createChild("img", "resource-status-image");
344             statusCodeImage.src = statusImageSource;
345             statusCodeImage.title = this._request.statusCode + " " + this._request.statusText;
346             var value = statusCodeFragment.createChild("div", "header-value source-code");
347             value.textContent = this._request.statusCode + " " + this._request.statusText;
348             if (this._request.cached)
349                 value.createChild("span", "status-from-cache").textContent = " " + WebInspector.UIString("(from cache)");
350
351             statusCodeElement.title = statusCodeFragment;
352         }
353     },
354
355     _refreshHeadersTitle: function(title, headersTreeElement, headersLength)
356     {
357         headersTreeElement.listItemElement.removeChildren();
358         headersTreeElement.listItemElement.appendChild(document.createTextNode(title));
359
360         var headerCount = document.createElement("span");
361         headerCount.addStyleClass("header-count");
362         headerCount.textContent = WebInspector.UIString(" (%d)", headersLength);
363         headersTreeElement.listItemElement.appendChild(headerCount);
364     },
365
366     _refreshHeaders: function(title, headers, additionalRow, headersTreeElement)
367     {
368         headersTreeElement.removeChildren();
369
370         var length = headers.length;
371         this._refreshHeadersTitle(title, headersTreeElement, length);
372         headersTreeElement.hidden = !length;
373         for (var i = 0; i < length; ++i) {
374             var headerTreeElement = new TreeElement(null, null, false);
375             headerTreeElement.title = this._formatHeader(headers[i].name, headers[i].value);
376             headerTreeElement.selectable = false;
377             headersTreeElement.appendChild(headerTreeElement);
378         }
379
380         if (additionalRow) {
381             var headerTreeElement = new TreeElement(null, null, false);
382             headerTreeElement.title = this._formatHeader(additionalRow.name, additionalRow.value);
383             headerTreeElement.selectable = false;
384             headersTreeElement.appendChild(headerTreeElement);
385         }
386     },
387
388     _refreshHeadersText: function(title, headers, headersText, headersTreeElement)
389     {
390         this._populateTreeElementWithSourceText(headersTreeElement, headersText);
391         this._refreshHeadersTitle(title, headersTreeElement, headers.length);
392     },
393
394     _toggleRequestHeadersText: function(event)
395     {
396         this._showRequestHeadersText = !this._showRequestHeadersText;
397         this._refreshRequestHeaders();
398     },
399
400     _toggleResponseHeadersText: function(event)
401     {
402         this._showResponseHeadersText = !this._showResponseHeadersText;
403         this._refreshResponseHeaders();
404     },
405
406     _createToggleButton: function(title)
407     {
408         var button = document.createElement("span");
409         button.addStyleClass("header-toggle");
410         button.textContent = title;
411         return button;
412     },
413
414     _createHeadersToggleButton: function(isHeadersTextShown)
415     {
416         var toggleTitle = isHeadersTextShown ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source");
417         return this._createToggleButton(toggleTitle);
418     },
419
420     __proto__: WebInspector.View.prototype
421 }