• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
36WebInspector.RequestHeadersView = function(request)
37{
38    WebInspector.View.call(this);
39    this.registerRequiredCSS("resourceView.css");
40    this.element.classList.add("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._formDataTreeElement = new TreeElement("", null, true);
80    this._formDataTreeElement.expanded = true;
81    this._formDataTreeElement.selectable = false;
82    this._formDataTreeElement.hidden = true;
83    this._headersTreeOutline.appendChild(this._formDataTreeElement);
84
85    this._requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true);
86    this._requestPayloadTreeElement.expanded = true;
87    this._requestPayloadTreeElement.selectable = false;
88    this._requestPayloadTreeElement.hidden = true;
89    this._headersTreeOutline.appendChild(this._requestPayloadTreeElement);
90
91    this._responseHeadersTreeElement = new TreeElement("", null, true);
92    this._responseHeadersTreeElement.expanded = true;
93    this._responseHeadersTreeElement.selectable = false;
94    this._headersTreeOutline.appendChild(this._responseHeadersTreeElement);
95}
96
97WebInspector.RequestHeadersView.prototype = {
98
99    wasShown: function()
100    {
101        this._request.addEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
102        this._request.addEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
103        this._request.addEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
104
105        this._refreshURL();
106        this._refreshQueryString();
107        this._refreshRequestHeaders();
108        this._refreshResponseHeaders();
109        this._refreshHTTPInformation();
110    },
111
112    willHide: function()
113    {
114        this._request.removeEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
115        this._request.removeEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
116        this._request.removeEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
117    },
118
119    /**
120     * @param {string} name
121     * @param {string} value
122     * @return {!DocumentFragment}
123     */
124    _formatHeader: function(name, value)
125    {
126        var fragment = document.createDocumentFragment();
127        fragment.createChild("div", "header-name").textContent = name + ":";
128        fragment.createChild("div", "header-value source-code").textContent = value;
129
130        return fragment;
131    },
132
133    /**
134     * @param {string} value
135     * @param {string} className
136     * @param {boolean} decodeParameters
137     * @return {!Element}
138     */
139    _formatParameter: function(value, className, decodeParameters)
140    {
141        var errorDecoding = false;
142
143        if (decodeParameters) {
144            value = value.replace(/\+/g, " ");
145            if (value.indexOf("%") >= 0) {
146                try {
147                    value = decodeURIComponent(value);
148                } catch (e) {
149                    errorDecoding = true;
150                }
151            }
152        }
153        var div = document.createElement("div");
154        div.className = className;
155        if (errorDecoding)
156            div.createChild("span", "error-message").textContent = WebInspector.UIString("(unable to decode value)");
157        else
158            div.textContent = value;
159        return div;
160    },
161
162    _refreshURL: function()
163    {
164        this._urlTreeElement.title = this._formatHeader(WebInspector.UIString("Request URL"), this._request.url);
165    },
166
167    _refreshQueryString: function()
168    {
169        var queryString = this._request.queryString();
170        var queryParameters = this._request.queryParameters;
171        this._queryStringTreeElement.hidden = !queryParameters;
172        if (queryParameters)
173            this._refreshParams(WebInspector.UIString("Query String Parameters"), queryParameters, queryString, this._queryStringTreeElement);
174    },
175
176    _refreshFormData: function()
177    {
178        this._formDataTreeElement.hidden = true;
179        this._requestPayloadTreeElement.hidden = true;
180
181        var formData = this._request.requestFormData;
182        if (!formData)
183            return;
184
185        var formParameters = this._request.formParameters;
186        if (formParameters) {
187            this._formDataTreeElement.hidden = false;
188            this._refreshParams(WebInspector.UIString("Form Data"), formParameters, formData, this._formDataTreeElement);
189        } else {
190            this._requestPayloadTreeElement.hidden = false;
191            try {
192                var json = JSON.parse(formData);
193                this._refreshRequestJSONPayload(json, formData);
194            } catch (e) {
195                this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, formData);
196            }
197        }
198    },
199
200    /**
201     * @param {!TreeElement} treeElement
202     * @param {?string} sourceText
203     */
204    _populateTreeElementWithSourceText: function(treeElement, sourceText)
205    {
206        var sourceTextElement = document.createElement("span");
207        sourceTextElement.classList.add("header-value");
208        sourceTextElement.classList.add("source-code");
209        sourceTextElement.textContent = String(sourceText || "").trim();
210
211        var sourceTreeElement = new TreeElement(sourceTextElement);
212        sourceTreeElement.selectable = false;
213        treeElement.removeChildren();
214        treeElement.appendChild(sourceTreeElement);
215    },
216
217    /**
218     * @param {string} title
219     * @param {?Array.<!WebInspector.NetworkRequest.NameValue>} params
220     * @param {?string} sourceText
221     * @param {!TreeElement} paramsTreeElement
222     */
223    _refreshParams: function(title, params, sourceText, paramsTreeElement)
224    {
225        paramsTreeElement.removeChildren();
226
227        paramsTreeElement.listItemElement.removeChildren();
228        paramsTreeElement.listItemElement.appendChild(document.createTextNode(title));
229
230        var headerCount = document.createElement("span");
231        headerCount.classList.add("header-count");
232        headerCount.textContent = WebInspector.UIString(" (%d)", params.length);
233        paramsTreeElement.listItemElement.appendChild(headerCount);
234
235        /**
236         * @param {?Event} event
237         * @this {WebInspector.RequestHeadersView}
238         */
239        function toggleViewSource(event)
240        {
241            paramsTreeElement._viewSource = !paramsTreeElement._viewSource;
242            this._refreshParams(title, params, sourceText, paramsTreeElement);
243        }
244
245        paramsTreeElement.listItemElement.appendChild(this._createViewSourceToggle(paramsTreeElement._viewSource, toggleViewSource.bind(this)));
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), false);
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    /**
271     * @param {*} parsedObject
272     * @param {string} sourceText
273     */
274    _refreshRequestJSONPayload: function(parsedObject, sourceText)
275    {
276        var treeElement = this._requestPayloadTreeElement;
277        treeElement.removeChildren();
278
279        var listItem = this._requestPayloadTreeElement.listItemElement;
280        listItem.removeChildren();
281        listItem.appendChild(document.createTextNode(this._requestPayloadTreeElement.title));
282
283        /**
284         * @param {?Event} event
285         * @this {WebInspector.RequestHeadersView}
286         */
287        function toggleViewSource(event)
288        {
289            treeElement._viewSource = !treeElement._viewSource;
290            this._refreshRequestJSONPayload(parsedObject, sourceText);
291        }
292
293        listItem.appendChild(this._createViewSourceToggle(treeElement._viewSource, toggleViewSource.bind(this)));
294        if (treeElement._viewSource) {
295            this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, sourceText);
296        } else {
297            var object = WebInspector.RemoteObject.fromLocalObject(parsedObject);
298            var section = new WebInspector.ObjectPropertiesSection(object, object.description);
299            section.expand();
300            section.editable = false;
301            listItem.appendChild(section.element);
302        }
303    },
304
305    /**
306     * @param {boolean} viewSource
307     * @param {function(?Event)} handler
308     * @return {!Element}
309     */
310    _createViewSourceToggle: function(viewSource, handler)
311    {
312        var viewSourceToggleTitle = viewSource ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source");
313        var viewSourceToggleButton = this._createToggleButton(viewSourceToggleTitle);
314        viewSourceToggleButton.addEventListener("click", handler, false);
315        return viewSourceToggleButton;
316    },
317
318    /**
319     * @param {?Event} event
320     */
321    _toggleURLDecoding: function(event)
322    {
323        this._decodeRequestParameters = !this._decodeRequestParameters;
324        this._refreshQueryString();
325        this._refreshFormData();
326    },
327
328    _refreshRequestHeaders: function()
329    {
330        var treeElement = this._requestHeadersTreeElement;
331
332        var headers = this._request.requestHeaders();
333        headers = headers.slice();
334        headers.sort(function(a, b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()) });
335        var headersText = this._request.requestHeadersText();
336
337        if (this._showRequestHeadersText && headersText)
338            this._refreshHeadersText(WebInspector.UIString("Request Headers"), headers.length, headersText, treeElement);
339        else
340            this._refreshHeaders(WebInspector.UIString("Request Headers"), headers, treeElement);
341
342        if (headersText === undefined) {
343            var caution = WebInspector.UIString(" CAUTION: Provisional headers are shown.");
344            treeElement.listItemElement.createChild("span", "caution").textContent = caution;
345        }
346
347        if (headersText) {
348            var toggleButton = this._createHeadersToggleButton(this._showRequestHeadersText);
349            toggleButton.addEventListener("click", this._toggleRequestHeadersText.bind(this), false);
350            treeElement.listItemElement.appendChild(toggleButton);
351        }
352
353        this._refreshFormData();
354    },
355
356    _refreshResponseHeaders: function()
357    {
358        var treeElement = this._responseHeadersTreeElement;
359        var headers = this._request.sortedResponseHeaders;
360        var headersText = this._request.responseHeadersText;
361
362        if (this._showResponseHeadersText)
363            this._refreshHeadersText(WebInspector.UIString("Response Headers"), headers.length, headersText, treeElement);
364        else
365            this._refreshHeaders(WebInspector.UIString("Response Headers"), headers, treeElement);
366
367        if (headersText) {
368            var toggleButton = this._createHeadersToggleButton(this._showResponseHeadersText);
369            toggleButton.addEventListener("click", this._toggleResponseHeadersText.bind(this), false);
370            treeElement.listItemElement.appendChild(toggleButton);
371        }
372    },
373
374    _refreshHTTPInformation: function()
375    {
376        var requestMethodElement = this._requestMethodTreeElement;
377        requestMethodElement.hidden = !this._request.statusCode;
378        var statusCodeElement = this._statusCodeTreeElement;
379        statusCodeElement.hidden = !this._request.statusCode;
380
381        if (this._request.statusCode) {
382            var statusCodeFragment = document.createDocumentFragment();
383            statusCodeFragment.createChild("div", "header-name").textContent = WebInspector.UIString("Status Code") + ":";
384
385            var statusCodeImage = statusCodeFragment.createChild("div", "resource-status-image");
386            statusCodeImage.title = this._request.statusCode + " " + this._request.statusText;
387
388            if (this._request.statusCode < 300 || this._request.statusCode === 304)
389                statusCodeImage.classList.add("green-ball");
390            else if (this._request.statusCode < 400)
391                statusCodeImage.classList.add("orange-ball");
392            else
393                statusCodeImage.classList.add("red-ball");
394
395            requestMethodElement.title = this._formatHeader(WebInspector.UIString("Request Method"), this._request.requestMethod);
396
397            var value = statusCodeFragment.createChild("div", "header-value source-code");
398            value.textContent = this._request.statusCode + " " + this._request.statusText;
399            if (this._request.cached)
400                value.createChild("span", "status-from-cache").textContent = " " + WebInspector.UIString("(from cache)");
401
402            statusCodeElement.title = statusCodeFragment;
403        }
404    },
405
406    /**
407     * @param {string} title
408     * @param {!TreeElement} headersTreeElement
409     * @param {number} headersLength
410     */
411    _refreshHeadersTitle: function(title, headersTreeElement, headersLength)
412    {
413        headersTreeElement.listItemElement.removeChildren();
414        headersTreeElement.listItemElement.createTextChild(title);
415
416        var headerCount = WebInspector.UIString(" (%d)", headersLength);
417        headersTreeElement.listItemElement.createChild("span", "header-count").textContent = headerCount;
418    },
419
420    /**
421     * @param {string} title
422     * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
423     * @param {!TreeElement} headersTreeElement
424     */
425    _refreshHeaders: function(title, headers, headersTreeElement)
426    {
427        headersTreeElement.removeChildren();
428
429        var length = headers.length;
430        this._refreshHeadersTitle(title, headersTreeElement, length);
431        headersTreeElement.hidden = !length;
432        for (var i = 0; i < length; ++i) {
433            var headerTreeElement = new TreeElement(this._formatHeader(headers[i].name, headers[i].value));
434            headerTreeElement.selectable = false;
435            headersTreeElement.appendChild(headerTreeElement);
436        }
437    },
438
439    /**
440     * @param {string} title
441     * @param {number} count
442     * @param {string} headersText
443     * @param {!TreeElement} headersTreeElement
444     */
445    _refreshHeadersText: function(title, count, headersText, headersTreeElement)
446    {
447        this._populateTreeElementWithSourceText(headersTreeElement, headersText);
448        this._refreshHeadersTitle(title, headersTreeElement, count);
449    },
450
451    /**
452     * @param {?Event} event
453     */
454    _toggleRequestHeadersText: function(event)
455    {
456        this._showRequestHeadersText = !this._showRequestHeadersText;
457        this._refreshRequestHeaders();
458    },
459
460    /**
461     * @param {?Event} event
462     */
463    _toggleResponseHeadersText: function(event)
464    {
465        this._showResponseHeadersText = !this._showResponseHeadersText;
466        this._refreshResponseHeaders();
467    },
468
469    /**
470     * @param {string} title
471     * @return {!Element}
472     */
473    _createToggleButton: function(title)
474    {
475        var button = document.createElement("span");
476        button.classList.add("header-toggle");
477        button.textContent = title;
478        return button;
479    },
480
481    /**
482     * @param {boolean} isHeadersTextShown
483     * @return {!Element}
484     */
485    _createHeadersToggleButton: function(isHeadersTextShown)
486    {
487        var toggleTitle = isHeadersTextShown ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source");
488        return this._createToggleButton(toggleTitle);
489    },
490
491    __proto__: WebInspector.View.prototype
492}
493