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.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 
     97 WebInspector.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