Home | History | Annotate | Download | only in front_end
      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 
    103 WebInspector.RequestHeadersView.prototype = {
    104 
    105     wasShown: function()
    106     {
    107         this._request.addEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
    108         this._request.addEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
    109         this._request.addEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
    110 
    111         this._refreshURL();
    112         this._refreshQueryString();
    113         this._refreshUrlFragment();
    114         this._refreshRequestHeaders();
    115         this._refreshResponseHeaders();
    116         this._refreshHTTPInformation();
    117     },
    118 
    119     willHide: function()
    120     {
    121         this._request.removeEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
    122         this._request.removeEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
    123         this._request.removeEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
    124     },
    125 
    126     /**
    127      * @param {string} name
    128      * @param {string} value
    129      */
    130     _formatHeader: function(name, value)
    131     {
    132         var fragment = document.createDocumentFragment();
    133         fragment.createChild("div", "header-name").textContent = name + ":";
    134         fragment.createChild("div", "header-value source-code").textContent = value;
    135 
    136         return fragment;
    137     },
    138 
    139     /**
    140      * @param {string} value
    141      * @param {string} className
    142      * @param {boolean} decodeParameters
    143      */
    144     _formatParameter: function(value, className, decodeParameters)
    145     {
    146         var errorDecoding = false;
    147 
    148         if (decodeParameters) {
    149             value = value.replace(/\+/g, " ");
    150             if (value.indexOf("%") >= 0) {
    151                 try {
    152                     value = decodeURIComponent(value);
    153                 } catch (e) {
    154                     errorDecoding = true;
    155                 }
    156             }
    157         }
    158         var div = document.createElement("div");
    159         div.className = className;
    160         if (errorDecoding)
    161             div.createChild("span", "error-message").textContent = WebInspector.UIString("(unable to decode value)");
    162         else
    163             div.textContent = value;
    164         return div;
    165     },
    166 
    167     _refreshURL: function()
    168     {
    169         this._urlTreeElement.title = this._formatHeader(WebInspector.UIString("Request URL"), this._request.url);
    170     },
    171 
    172     _refreshQueryString: function()
    173     {
    174         var queryString = this._request.queryString();
    175         var queryParameters = this._request.queryParameters;
    176         this._queryStringTreeElement.hidden = !queryParameters;
    177         if (queryParameters)
    178             this._refreshParams(WebInspector.UIString("Query String Parameters"), queryParameters, queryString, this._queryStringTreeElement);
    179     },
    180 
    181     _refreshUrlFragment: function()
    182     {
    183         var urlFragment = this._request.parsedURL.fragment;
    184         this._urlFragmentTreeElement.hidden = !urlFragment;
    185 
    186         if (!urlFragment)
    187             return;
    188 
    189         var sectionTitle = WebInspector.UIString("URL fragment");
    190 
    191         this._urlFragmentTreeElement.removeChildren();
    192         this._urlFragmentTreeElement.listItemElement.removeChildren();
    193         this._urlFragmentTreeElement.listItemElement.appendChild(document.createTextNode(sectionTitle));
    194 
    195         var fragmentTreeElement = new TreeElement(null, null, false);
    196         fragmentTreeElement.title = this._formatHeader("#", urlFragment);
    197         fragmentTreeElement.selectable = false;
    198         this._urlFragmentTreeElement.appendChild(fragmentTreeElement);
    199     },
    200 
    201     _refreshFormData: function()
    202     {
    203         this._formDataTreeElement.hidden = true;
    204         this._requestPayloadTreeElement.hidden = true;
    205 
    206         var formData = this._request.requestFormData;
    207         if (!formData)
    208             return;
    209 
    210         var formParameters = this._request.formParameters;
    211         if (formParameters) {
    212             this._formDataTreeElement.hidden = false;
    213             this._refreshParams(WebInspector.UIString("Form Data"), formParameters, formData, this._formDataTreeElement);
    214         } else {
    215             this._requestPayloadTreeElement.hidden = false;
    216             try {
    217                 var json = JSON.parse(formData);
    218                 this._refreshRequestJSONPayload(json, formData, false);
    219             } catch (e) {
    220                 this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, formData);
    221             }
    222         }
    223     },
    224 
    225     _populateTreeElementWithSourceText: function(treeElement, sourceText)
    226     {
    227         treeElement.removeChildren();
    228 
    229         var sourceTreeElement = new TreeElement(null, null, false);
    230         sourceTreeElement.selectable = false;
    231         treeElement.appendChild(sourceTreeElement);
    232 
    233         var sourceTextElement = document.createElement("span");
    234         sourceTextElement.addStyleClass("header-value");
    235         sourceTextElement.addStyleClass("source-code");
    236         sourceTextElement.textContent = String(sourceText).trim();
    237         sourceTreeElement.listItemElement.appendChild(sourceTextElement);
    238     },
    239 
    240     _refreshParams: function(title, params, sourceText, paramsTreeElement)
    241     {
    242         paramsTreeElement.removeChildren();
    243 
    244         paramsTreeElement.listItemElement.removeChildren();
    245         paramsTreeElement.listItemElement.appendChild(document.createTextNode(title));
    246 
    247         var headerCount = document.createElement("span");
    248         headerCount.addStyleClass("header-count");
    249         headerCount.textContent = WebInspector.UIString(" (%d)", params.length);
    250         paramsTreeElement.listItemElement.appendChild(headerCount);
    251 
    252         function toggleViewSource()
    253         {
    254             paramsTreeElement._viewSource = !paramsTreeElement._viewSource;
    255             this._refreshParams(title, params, sourceText, paramsTreeElement);
    256         }
    257 
    258         paramsTreeElement.listItemElement.appendChild(this._createViewSourceToggle(paramsTreeElement._viewSource, toggleViewSource.bind(this)));
    259 
    260         if (paramsTreeElement._viewSource) {
    261             this._populateTreeElementWithSourceText(paramsTreeElement, sourceText);
    262             return;
    263         }
    264 
    265         var toggleTitle = this._decodeRequestParameters ? WebInspector.UIString("view URL encoded") : WebInspector.UIString("view decoded");
    266         var toggleButton = this._createToggleButton(toggleTitle);
    267         toggleButton.addEventListener("click", this._toggleURLDecoding.bind(this));
    268         paramsTreeElement.listItemElement.appendChild(toggleButton);
    269 
    270         for (var i = 0; i < params.length; ++i) {
    271             var paramNameValue = document.createDocumentFragment();
    272             var name = this._formatParameter(params[i].name + ":", "header-name", this._decodeRequestParameters);
    273             var value = this._formatParameter(params[i].value, "header-value source-code", this._decodeRequestParameters);
    274             paramNameValue.appendChild(name);
    275             paramNameValue.appendChild(value);
    276 
    277             var parmTreeElement = new TreeElement(paramNameValue, null, false);
    278             parmTreeElement.selectable = false;
    279             paramsTreeElement.appendChild(parmTreeElement);
    280         }
    281     },
    282 
    283     /**
    284      * @param {*} parsedObject
    285      * @param {string} sourceText
    286      * @param {boolean} viewSource
    287      */
    288     _refreshRequestJSONPayload: function(parsedObject, sourceText, viewSource)
    289     {
    290         this._requestPayloadTreeElement.removeChildren();
    291 
    292         var listItem = this._requestPayloadTreeElement.listItemElement;
    293         listItem.removeChildren();
    294         listItem.appendChild(document.createTextNode(this._requestPayloadTreeElement.title));
    295 
    296         var setViewSource = this._refreshRequestJSONPayload.bind(this, parsedObject, sourceText);
    297 
    298         if (viewSource) {
    299             listItem.appendChild(this._createViewSourceToggle(true, setViewSource.bind(this, false)));
    300             this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, sourceText);
    301         } else {
    302             listItem.appendChild(this._createViewSourceToggle(false, setViewSource.bind(this, true)));
    303             var object = WebInspector.RemoteObject.fromLocalObject(parsedObject);
    304             var section = new WebInspector.ObjectPropertiesSection(object, object.description);
    305             section.expand();
    306             section.editable = false;
    307             listItem.appendChild(section.element);
    308         }
    309     },
    310 
    311     /**
    312      * @param {boolean} viewSource
    313      * @param {Function} handler
    314      */
    315     _createViewSourceToggle: function(viewSource, handler)
    316     {
    317         var viewSourceToggleTitle = viewSource ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source");
    318         var viewSourceToggleButton = this._createToggleButton(viewSourceToggleTitle);
    319         viewSourceToggleButton.addEventListener("click", handler);
    320         return viewSourceToggleButton;
    321     },
    322 
    323     _toggleURLDecoding: function(event)
    324     {
    325         this._decodeRequestParameters = !this._decodeRequestParameters;
    326         this._refreshQueryString();
    327         this._refreshFormData();
    328     },
    329 
    330     _getHeaderValue: function(headers, key)
    331     {
    332         var lowerKey = key.toLowerCase();
    333         for (var testKey in headers) {
    334             if (testKey.toLowerCase() === lowerKey)
    335                 return headers[testKey];
    336         }
    337     },
    338 
    339     _refreshRequestHeaders: function()
    340     {
    341         if (this._showRequestHeadersText)
    342             this._refreshHeadersText(WebInspector.UIString("Request Headers"), this._request.sortedRequestHeaders, this._request.requestHeadersText, this._requestHeadersTreeElement);
    343         else
    344             this._refreshHeaders(WebInspector.UIString("Request Headers"), this._request.sortedRequestHeaders, this._requestHeadersTreeElement);
    345 
    346         if (this._request.requestHeadersText) {
    347             var toggleButton = this._createHeadersToggleButton(this._showRequestHeadersText);
    348             toggleButton.addEventListener("click", this._toggleRequestHeadersText.bind(this));
    349             this._requestHeadersTreeElement.listItemElement.appendChild(toggleButton);
    350         }
    351 
    352         this._refreshFormData();
    353     },
    354 
    355     _refreshResponseHeaders: function()
    356     {
    357         if (this._showResponseHeadersText)
    358             this._refreshHeadersText(WebInspector.UIString("Response Headers"), this._request.sortedResponseHeaders, this._request.responseHeadersText, this._responseHeadersTreeElement);
    359         else
    360             this._refreshHeaders(WebInspector.UIString("Response Headers"), this._request.sortedResponseHeaders, this._responseHeadersTreeElement);
    361 
    362         if (this._request.responseHeadersText) {
    363             var toggleButton = this._createHeadersToggleButton(this._showResponseHeadersText);
    364             toggleButton.addEventListener("click", this._toggleResponseHeadersText.bind(this));
    365             this._responseHeadersTreeElement.listItemElement.appendChild(toggleButton);
    366         }
    367     },
    368 
    369     _refreshHTTPInformation: function()
    370     {
    371         var requestMethodElement = this._requestMethodTreeElement;
    372         requestMethodElement.hidden = !this._request.statusCode;
    373         var statusCodeElement = this._statusCodeTreeElement;
    374         statusCodeElement.hidden = !this._request.statusCode;
    375 
    376         if (this._request.statusCode) {
    377             var statusCodeFragment = document.createDocumentFragment();
    378             statusCodeFragment.createChild("div", "header-name").textContent = WebInspector.UIString("Status Code") + ":";
    379 
    380             var statusCodeImage = statusCodeFragment.createChild("div", "resource-status-image");
    381             statusCodeImage.title = this._request.statusCode + " " + this._request.statusText;
    382 
    383             if (this._request.statusCode < 300 || this._request.statusCode === 304)
    384                 statusCodeImage.addStyleClass("green-ball");
    385             else if (this._request.statusCode < 400)
    386                 statusCodeImage.addStyleClass("orange-ball");
    387             else
    388                 statusCodeImage.addStyleClass("red-ball");
    389 
    390             requestMethodElement.title = this._formatHeader(WebInspector.UIString("Request Method"), this._request.requestMethod);
    391 
    392             var value = statusCodeFragment.createChild("div", "header-value source-code");
    393             value.textContent = this._request.statusCode + " " + this._request.statusText;
    394             if (this._request.cached)
    395                 value.createChild("span", "status-from-cache").textContent = " " + WebInspector.UIString("(from cache)");
    396 
    397             statusCodeElement.title = statusCodeFragment;
    398         }
    399     },
    400 
    401     _refreshHeadersTitle: function(title, headersTreeElement, headersLength)
    402     {
    403         headersTreeElement.listItemElement.removeChildren();
    404         headersTreeElement.listItemElement.appendChild(document.createTextNode(title));
    405 
    406         var headerCount = document.createElement("span");
    407         headerCount.addStyleClass("header-count");
    408         headerCount.textContent = WebInspector.UIString(" (%d)", headersLength);
    409         headersTreeElement.listItemElement.appendChild(headerCount);
    410     },
    411 
    412     _refreshHeaders: function(title, headers, headersTreeElement)
    413     {
    414         headersTreeElement.removeChildren();
    415 
    416         var length = headers.length;
    417         this._refreshHeadersTitle(title, headersTreeElement, length);
    418         headersTreeElement.hidden = !length;
    419         for (var i = 0; i < length; ++i) {
    420             var headerTreeElement = new TreeElement(null, null, false);
    421             headerTreeElement.title = this._formatHeader(headers[i].name, headers[i].value);
    422             headerTreeElement.selectable = false;
    423             headersTreeElement.appendChild(headerTreeElement);
    424         }
    425     },
    426 
    427     _refreshHeadersText: function(title, headers, headersText, headersTreeElement)
    428     {
    429         this._populateTreeElementWithSourceText(headersTreeElement, headersText);
    430         this._refreshHeadersTitle(title, headersTreeElement, headers.length);
    431     },
    432 
    433     _toggleRequestHeadersText: function(event)
    434     {
    435         this._showRequestHeadersText = !this._showRequestHeadersText;
    436         this._refreshRequestHeaders();
    437     },
    438 
    439     _toggleResponseHeadersText: function(event)
    440     {
    441         this._showResponseHeadersText = !this._showResponseHeadersText;
    442         this._refreshResponseHeaders();
    443     },
    444 
    445     _createToggleButton: function(title)
    446     {
    447         var button = document.createElement("span");
    448         button.addStyleClass("header-toggle");
    449         button.textContent = title;
    450         return button;
    451     },
    452 
    453     _createHeadersToggleButton: function(isHeadersTextShown)
    454     {
    455         var toggleTitle = isHeadersTextShown ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source");
    456         return this._createToggleButton(toggleTitle);
    457     },
    458 
    459     __proto__: WebInspector.View.prototype
    460 }
    461