• 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 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30WebInspector.ResourceView = function(resource)
31{
32    WebInspector.View.call(this);
33
34    this.element.addStyleClass("resource-view");
35
36    this.resource = resource;
37
38    this.tabsElement = document.createElement("div");
39    this.tabsElement.className = "scope-bar";
40    this.element.appendChild(this.tabsElement);
41
42    this.headersTabElement = document.createElement("li");
43    this.headersTabElement.textContent = WebInspector.UIString("Headers");
44    this.contentTabElement = document.createElement("li");
45    this.contentTabElement.textContent = WebInspector.UIString("Content");
46    this.tabsElement.appendChild(this.headersTabElement);
47    this.tabsElement.appendChild(this.contentTabElement);
48
49    this.headersTabElement.addEventListener("click", this._selectHeadersTab.bind(this), false);
50    this.contentTabElement.addEventListener("click", this._selectContentTab.bind(this), false);
51
52    this.headersElement = document.createElement("div");
53    this.headersElement.className = "resource-view-headers";
54    this.element.appendChild(this.headersElement);
55
56    this.contentElement = document.createElement("div");
57    this.contentElement.className = "resource-view-content";
58    this.element.appendChild(this.contentElement);
59
60    this.headersListElement = document.createElement("ol");
61    this.headersListElement.className = "outline-disclosure";
62    this.headersElement.appendChild(this.headersListElement);
63
64    this.headersTreeOutline = new TreeOutline(this.headersListElement);
65    this.headersTreeOutline.expandTreeElementsWhenArrowing = true;
66
67    this.urlTreeElement = new TreeElement("", null, false);
68    this.urlTreeElement.selectable = false;
69    this.headersTreeOutline.appendChild(this.urlTreeElement);
70
71    this.requestMethodTreeElement = new TreeElement("", null, false);
72    this.requestMethodTreeElement.selectable = false;
73    this.headersTreeOutline.appendChild(this.requestMethodTreeElement);
74
75    this.statusCodeTreeElement = new TreeElement("", null, false);
76    this.statusCodeTreeElement.selectable = false;
77    this.headersTreeOutline.appendChild(this.statusCodeTreeElement);
78
79    this.requestHeadersTreeElement = new TreeElement("", null, true);
80    this.requestHeadersTreeElement.expanded = true;
81    this.requestHeadersTreeElement.selectable = false;
82    this.headersTreeOutline.appendChild(this.requestHeadersTreeElement);
83
84    this._decodeHover = WebInspector.UIString("Double-Click to toggle between URL encoded and decoded formats");
85    this._decodeRequestParameters = true;
86
87    this.queryStringTreeElement = new TreeElement("", null, true);
88    this.queryStringTreeElement.expanded = true;
89    this.queryStringTreeElement.selectable = false;
90    this.queryStringTreeElement.hidden = true;
91    this.headersTreeOutline.appendChild(this.queryStringTreeElement);
92
93    this.formDataTreeElement = new TreeElement("", null, true);
94    this.formDataTreeElement.expanded = true;
95    this.formDataTreeElement.selectable = false;
96    this.formDataTreeElement.hidden = true;
97    this.headersTreeOutline.appendChild(this.formDataTreeElement);
98
99    this.requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true);
100    this.requestPayloadTreeElement.expanded = true;
101    this.requestPayloadTreeElement.selectable = false;
102    this.requestPayloadTreeElement.hidden = true;
103    this.headersTreeOutline.appendChild(this.requestPayloadTreeElement);
104
105    this.responseHeadersTreeElement = new TreeElement("", null, true);
106    this.responseHeadersTreeElement.expanded = true;
107    this.responseHeadersTreeElement.selectable = false;
108    this.headersTreeOutline.appendChild(this.responseHeadersTreeElement);
109
110    this.headersVisible = true;
111
112    resource.addEventListener("url changed", this._refreshURL, this);
113    resource.addEventListener("requestHeaders changed", this._refreshRequestHeaders, this);
114    resource.addEventListener("responseHeaders changed", this._refreshResponseHeaders, this);
115    resource.addEventListener("finished", this._refreshHTTPInformation, this);
116
117    this._refreshURL();
118    this._refreshRequestHeaders();
119    this._refreshResponseHeaders();
120    this._refreshHTTPInformation();
121    this._selectTab();
122}
123
124WebInspector.ResourceView.prototype = {
125    attach: function()
126    {
127        if (!this.element.parentNode) {
128            var parentElement = (document.getElementById("resource-views") || document.getElementById("script-resource-views"));
129            if (parentElement)
130                parentElement.appendChild(this.element);
131        }
132    },
133
134    show: function(parentElement)
135    {
136        WebInspector.View.prototype.show.call(this, parentElement);
137        this._selectTab();
138    },
139
140    set headersVisible(x)
141    {
142        if (x === this._headersVisible)
143            return;
144        this._headersVisible = x;
145        if (x)
146            this.element.addStyleClass("headers-visible");
147        else
148            this.element.removeStyleClass("headers-visible");
149        this._selectTab();
150    },
151
152    _selectTab: function()
153    {
154        if (this._headersVisible) {
155            if (WebInspector.settings.resourceViewTab === "headers")
156                this._selectHeadersTab();
157            else
158                this._selectContentTab();
159        } else
160            this._innerSelectContentTab();
161    },
162
163    _selectHeadersTab: function()
164    {
165        WebInspector.settings.resourceViewTab = "headers";
166        this.headersTabElement.addStyleClass("selected");
167        this.contentTabElement.removeStyleClass("selected");
168        this.headersElement.removeStyleClass("hidden");
169        this.contentElement.addStyleClass("hidden");
170    },
171
172    _selectContentTab: function()
173    {
174        WebInspector.settings.resourceViewTab = "content";
175        this._innerSelectContentTab();
176    },
177
178    _innerSelectContentTab: function()
179    {
180        this.contentTabElement.addStyleClass("selected");
181        this.headersTabElement.removeStyleClass("selected");
182        this.contentElement.removeStyleClass("hidden");
183        this.headersElement.addStyleClass("hidden");
184        if ("resize" in this)
185            this.resize();
186        if ("contentTabSelected" in this)
187            this.contentTabSelected();
188    },
189
190    _refreshURL: function()
191    {
192        this.urlTreeElement.title = "<div class=\"header-name\">" + WebInspector.UIString("Request URL") + ":</div>" +
193            "<div class=\"header-value source-code\">" + this.resource.url.escapeHTML() + "</div>";
194    },
195
196    _refreshQueryString: function()
197    {
198        var url = this.resource.url;
199        var hasQueryString = url.indexOf("?") >= 0;
200
201        if (!hasQueryString) {
202            this.queryStringTreeElement.hidden = true;
203            return;
204        }
205
206        this.queryStringTreeElement.hidden = false;
207        var parmString = url.split("?", 2)[1];
208        this._refreshParms(WebInspector.UIString("Query String Parameters"), parmString, this.queryStringTreeElement);
209    },
210
211    _refreshFormData: function()
212    {
213        this.formDataTreeElement.hidden = true;
214        this.requestPayloadTreeElement.hidden = true;
215
216        var isFormData = this.resource.requestFormData;
217        if (!isFormData)
218            return;
219
220        var isFormEncoded = false;
221        var requestContentType = this._getHeaderValue(this.resource.requestHeaders, "Content-Type");
222        if (requestContentType && requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
223            isFormEncoded = true;
224
225        if (isFormEncoded) {
226            this.formDataTreeElement.hidden = false;
227            this._refreshParms(WebInspector.UIString("Form Data"), this.resource.requestFormData, this.formDataTreeElement);
228        } else {
229            this.requestPayloadTreeElement.hidden = false;
230            this._refreshRequestPayload(this.resource.requestFormData);
231        }
232    },
233
234    _refreshRequestPayload: function(formData)
235    {
236        this.requestPayloadTreeElement.removeChildren();
237
238        var title = "<div class=\"header-name\">&nbsp;</div>";
239        title += "<div class=\"raw-form-data header-value source-code\">" + formData.escapeHTML() + "</div>";
240        var parmTreeElement = new TreeElement(title, null, false);
241        parmTreeElement.selectable = false;
242        this.requestPayloadTreeElement.appendChild(parmTreeElement);
243    },
244
245    _refreshParms: function(title, parmString, parmsTreeElement)
246    {
247        var parms = parmString.split("&");
248        for (var i = 0; i < parms.length; ++i) {
249            var parm = parms[i];
250            parm = parm.split("=", 2);
251            if (parm.length == 1)
252                parm.push("");
253            parms[i] = parm;
254        }
255
256        parmsTreeElement.removeChildren();
257
258        parmsTreeElement.title = title + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", parms.length) + "</span>";
259
260        for (var i = 0; i < parms.length; ++i) {
261            var key = parms[i][0];
262            var value = parms[i][1];
263
264            var errorDecoding = false;
265            if (this._decodeRequestParameters) {
266                if (value.indexOf("%") >= 0) {
267                    try {
268                        value = decodeURIComponent(value);
269                    } catch(e) {
270                        errorDecoding = true;
271                    }
272                }
273
274                value = value.replace(/\+/g, " ");
275            }
276
277            valueEscaped = value.escapeHTML();
278            if (errorDecoding)
279                valueEscaped += " <span class=\"error-message\">" + WebInspector.UIString("(unable to decode value)").escapeHTML() + "</span>";
280
281            var title = "<div class=\"header-name\">" + key.escapeHTML() + ":</div>";
282            title += "<div class=\"header-value source-code\">" + valueEscaped + "</div>";
283
284            var parmTreeElement = new TreeElement(title, null, false);
285            parmTreeElement.selectable = false;
286            parmTreeElement.tooltip = this._decodeHover;
287            parmTreeElement.ondblclick = this._toggleURLdecoding.bind(this);
288            parmsTreeElement.appendChild(parmTreeElement);
289        }
290    },
291
292    _toggleURLdecoding: function(event)
293    {
294        this._decodeRequestParameters = !this._decodeRequestParameters;
295        this._refreshQueryString();
296        this._refreshFormData();
297    },
298
299    _getHeaderValue: function(headers, key)
300    {
301        var lowerKey = key.toLowerCase();
302        for (var testKey in headers) {
303            if (testKey.toLowerCase() === lowerKey)
304                return headers[testKey];
305        }
306    },
307
308    _refreshRequestHeaders: function()
309    {
310        this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, this.requestHeadersTreeElement);
311        this._refreshFormData();
312    },
313
314    _refreshResponseHeaders: function()
315    {
316        this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, this.responseHeadersTreeElement);
317    },
318
319    _refreshHTTPInformation: function()
320    {
321        var requestMethodElement = this.requestMethodTreeElement;
322        requestMethodElement.hidden = !this.resource.statusCode;
323        var statusCodeElement = this.statusCodeTreeElement;
324        statusCodeElement.hidden = !this.resource.statusCode;
325        var statusCodeImage = "";
326
327        if (this.resource.statusCode) {
328            var statusImageSource = "";
329            if (this.resource.statusCode < 300)
330                statusImageSource = "Images/successGreenDot.png";
331            else if (this.resource.statusCode < 400)
332                statusImageSource = "Images/warningOrangeDot.png";
333            else
334                statusImageSource = "Images/errorRedDot.png";
335            statusCodeImage = "<img class=\"resource-status-image\" src=\"" + statusImageSource + "\" title=\"" + WebInspector.Resource.StatusTextForCode(this.resource.statusCode) + "\">";
336
337            requestMethodElement.title = "<div class=\"header-name\">" + WebInspector.UIString("Request Method") + ":</div>" +
338                "<div class=\"header-value source-code\">" + this.resource.requestMethod + "</div>";
339
340            statusCodeElement.title = "<div class=\"header-name\">" + WebInspector.UIString("Status Code") + ":</div>" +
341                statusCodeImage + "<div class=\"header-value source-code\">" + WebInspector.Resource.StatusTextForCode(this.resource.statusCode) + "</div>";
342        }
343    },
344
345    _refreshHeaders: function(title, headers, headersTreeElement)
346    {
347        headersTreeElement.removeChildren();
348
349        var length = headers.length;
350        headersTreeElement.title = title.escapeHTML() + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", length) + "</span>";
351        headersTreeElement.hidden = !length;
352
353        var length = headers.length;
354        for (var i = 0; i < length; ++i) {
355            var title = "<div class=\"header-name\">" + headers[i].header.escapeHTML() + ":</div>";
356            title += "<div class=\"header-value source-code\">" + headers[i].value.escapeHTML() + "</div>"
357
358            var headerTreeElement = new TreeElement(title, null, false);
359            headerTreeElement.selectable = false;
360            headersTreeElement.appendChild(headerTreeElement);
361        }
362    }
363}
364
365WebInspector.ResourceView.prototype.__proto__ = WebInspector.View.prototype;
366