Home | History | Annotate | Download | only in network
      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.VBox}
     34  * @param {!WebInspector.NetworkRequest} request
     35  */
     36 WebInspector.RequestHeadersView = function(request)
     37 {
     38     WebInspector.VBox.call(this);
     39     this.registerRequiredCSS("resourceView.css");
     40     this.registerRequiredCSS("requestHeadersView.css");
     41     this.element.classList.add("request-headers-view");
     42 
     43     this._request = request;
     44 
     45     this._headersListElement = document.createElement("ol");
     46     this._headersListElement.className = "outline-disclosure";
     47     this.element.appendChild(this._headersListElement);
     48 
     49     this._headersTreeOutline = new TreeOutline(this._headersListElement);
     50     this._headersTreeOutline.expandTreeElementsWhenArrowing = true;
     51 
     52     this._remoteAddressTreeElement = new TreeElement("", null, false);
     53     this._remoteAddressTreeElement.selectable = false;
     54     this._remoteAddressTreeElement.hidden = true;
     55     this._headersTreeOutline.appendChild(this._remoteAddressTreeElement);
     56 
     57     this._urlTreeElement = new TreeElement("", null, false);
     58     this._urlTreeElement.selectable = false;
     59     this._headersTreeOutline.appendChild(this._urlTreeElement);
     60 
     61     this._requestMethodTreeElement = new TreeElement("", null, false);
     62     this._requestMethodTreeElement.selectable = false;
     63     this._headersTreeOutline.appendChild(this._requestMethodTreeElement);
     64 
     65     this._statusCodeTreeElement = new TreeElement("", null, false);
     66     this._statusCodeTreeElement.selectable = false;
     67     this._headersTreeOutline.appendChild(this._statusCodeTreeElement);
     68 
     69     this._requestHeadersTreeElement = new TreeElement("", null, true);
     70     this._requestHeadersTreeElement.expanded = true;
     71     this._requestHeadersTreeElement.selectable = false;
     72     this._headersTreeOutline.appendChild(this._requestHeadersTreeElement);
     73 
     74     this._decodeRequestParameters = true;
     75 
     76     this._showRequestHeadersText = false;
     77     this._showResponseHeadersText = false;
     78 
     79     this._queryStringTreeElement = new TreeElement("", null, true);
     80     this._queryStringTreeElement.expanded = true;
     81     this._queryStringTreeElement.selectable = false;
     82     this._queryStringTreeElement.hidden = true;
     83     this._headersTreeOutline.appendChild(this._queryStringTreeElement);
     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.RemoteAddressChanged, this._refreshRemoteAddress, this);
    108         this._request.addEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
    109         this._request.addEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
    110         this._request.addEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
    111 
    112         this._refreshURL();
    113         this._refreshQueryString();
    114         this._refreshRequestHeaders();
    115         this._refreshResponseHeaders();
    116         this._refreshHTTPInformation();
    117         this._refreshRemoteAddress();
    118     },
    119 
    120     willHide: function()
    121     {
    122         this._request.removeEventListener(WebInspector.NetworkRequest.Events.RemoteAddressChanged, this._refreshRemoteAddress, this);
    123         this._request.removeEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
    124         this._request.removeEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
    125         this._request.removeEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
    126     },
    127 
    128     /**
    129      * @param {string} name
    130      * @param {string} value
    131      * @return {!DocumentFragment}
    132      */
    133     _formatHeader: function(name, value)
    134     {
    135         var fragment = document.createDocumentFragment();
    136         fragment.createChild("div", "header-name").textContent = name + ":";
    137         fragment.createChild("div", "header-value source-code").textContent = value;
    138 
    139         return fragment;
    140     },
    141 
    142     /**
    143      * @param {string} value
    144      * @param {string} className
    145      * @param {boolean} decodeParameters
    146      * @return {!Element}
    147      */
    148     _formatParameter: function(value, className, decodeParameters)
    149     {
    150         var errorDecoding = false;
    151 
    152         if (decodeParameters) {
    153             value = value.replace(/\+/g, " ");
    154             if (value.indexOf("%") >= 0) {
    155                 try {
    156                     value = decodeURIComponent(value);
    157                 } catch (e) {
    158                     errorDecoding = true;
    159                 }
    160             }
    161         }
    162         var div = document.createElement("div");
    163         div.className = className;
    164         if (errorDecoding)
    165             div.createChild("span", "error-message").textContent = WebInspector.UIString("(unable to decode value)");
    166         else
    167             div.textContent = value;
    168         return div;
    169     },
    170 
    171     _refreshURL: function()
    172     {
    173         this._urlTreeElement.title = this._formatHeader(WebInspector.UIString("Request URL"), this._request.url);
    174     },
    175 
    176     _refreshQueryString: function()
    177     {
    178         var queryString = this._request.queryString();
    179         var queryParameters = this._request.queryParameters;
    180         this._queryStringTreeElement.hidden = !queryParameters;
    181         if (queryParameters)
    182             this._refreshParams(WebInspector.UIString("Query String Parameters"), queryParameters, queryString, this._queryStringTreeElement);
    183     },
    184 
    185     _refreshFormData: function()
    186     {
    187         this._formDataTreeElement.hidden = true;
    188         this._requestPayloadTreeElement.hidden = true;
    189 
    190         var formData = this._request.requestFormData;
    191         if (!formData)
    192             return;
    193 
    194         var formParameters = this._request.formParameters;
    195         if (formParameters) {
    196             this._formDataTreeElement.hidden = false;
    197             this._refreshParams(WebInspector.UIString("Form Data"), formParameters, formData, this._formDataTreeElement);
    198         } else {
    199             this._requestPayloadTreeElement.hidden = false;
    200             try {
    201                 var json = JSON.parse(formData);
    202                 this._refreshRequestJSONPayload(json, formData);
    203             } catch (e) {
    204                 this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, formData);
    205             }
    206         }
    207     },
    208 
    209     /**
    210      * @param {!TreeElement} treeElement
    211      * @param {?string} sourceText
    212      */
    213     _populateTreeElementWithSourceText: function(treeElement, sourceText)
    214     {
    215         var sourceTextElement = document.createElement("span");
    216         sourceTextElement.classList.add("header-value");
    217         sourceTextElement.classList.add("source-code");
    218         sourceTextElement.textContent = String(sourceText || "").trim();
    219 
    220         var sourceTreeElement = new TreeElement(sourceTextElement);
    221         sourceTreeElement.selectable = false;
    222         treeElement.removeChildren();
    223         treeElement.appendChild(sourceTreeElement);
    224     },
    225 
    226     /**
    227      * @param {string} title
    228      * @param {?Array.<!WebInspector.NetworkRequest.NameValue>} params
    229      * @param {?string} sourceText
    230      * @param {!TreeElement} paramsTreeElement
    231      */
    232     _refreshParams: function(title, params, sourceText, paramsTreeElement)
    233     {
    234         paramsTreeElement.removeChildren();
    235 
    236         paramsTreeElement.listItemElement.removeChildren();
    237         paramsTreeElement.listItemElement.createTextChild(title);
    238 
    239         var headerCount = document.createElement("span");
    240         headerCount.classList.add("header-count");
    241         headerCount.textContent = WebInspector.UIString(" (%d)", params.length);
    242         paramsTreeElement.listItemElement.appendChild(headerCount);
    243 
    244         /**
    245          * @param {!Event} event
    246          * @this {WebInspector.RequestHeadersView}
    247          */
    248         function toggleViewSource(event)
    249         {
    250             paramsTreeElement._viewSource = !paramsTreeElement._viewSource;
    251             this._refreshParams(title, params, sourceText, paramsTreeElement);
    252         }
    253 
    254         paramsTreeElement.listItemElement.appendChild(this._createViewSourceToggle(paramsTreeElement._viewSource, toggleViewSource.bind(this)));
    255 
    256         if (paramsTreeElement._viewSource) {
    257             this._populateTreeElementWithSourceText(paramsTreeElement, sourceText);
    258             return;
    259         }
    260 
    261         var toggleTitle = this._decodeRequestParameters ? WebInspector.UIString("view URL encoded") : WebInspector.UIString("view decoded");
    262         var toggleButton = this._createToggleButton(toggleTitle);
    263         toggleButton.addEventListener("click", this._toggleURLDecoding.bind(this), false);
    264         paramsTreeElement.listItemElement.appendChild(toggleButton);
    265 
    266         for (var i = 0; i < params.length; ++i) {
    267             var paramNameValue = document.createDocumentFragment();
    268             var name = this._formatParameter(params[i].name + ":", "header-name", this._decodeRequestParameters);
    269             var value = this._formatParameter(params[i].value, "header-value source-code", this._decodeRequestParameters);
    270             paramNameValue.appendChild(name);
    271             paramNameValue.appendChild(value);
    272 
    273             var parmTreeElement = new TreeElement(paramNameValue, null, false);
    274             parmTreeElement.selectable = false;
    275             paramsTreeElement.appendChild(parmTreeElement);
    276         }
    277     },
    278 
    279     /**
    280      * @param {*} parsedObject
    281      * @param {string} sourceText
    282      */
    283     _refreshRequestJSONPayload: function(parsedObject, sourceText)
    284     {
    285         var treeElement = this._requestPayloadTreeElement;
    286         treeElement.removeChildren();
    287 
    288         var listItem = this._requestPayloadTreeElement.listItemElement;
    289         listItem.removeChildren();
    290         listItem.createTextChild(this._requestPayloadTreeElement.title);
    291 
    292         /**
    293          * @param {!Event} event
    294          * @this {WebInspector.RequestHeadersView}
    295          */
    296         function toggleViewSource(event)
    297         {
    298             treeElement._viewSource = !treeElement._viewSource;
    299             this._refreshRequestJSONPayload(parsedObject, sourceText);
    300         }
    301 
    302         listItem.appendChild(this._createViewSourceToggle(treeElement._viewSource, toggleViewSource.bind(this)));
    303         if (treeElement._viewSource) {
    304             this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, sourceText);
    305         } else {
    306             var object = WebInspector.RemoteObject.fromLocalObject(parsedObject);
    307             var section = new WebInspector.ObjectPropertiesSection(object, object.description);
    308             section.expand();
    309             section.editable = false;
    310             listItem.appendChild(section.element);
    311         }
    312     },
    313 
    314     /**
    315      * @param {boolean} viewSource
    316      * @param {function(!Event)} handler
    317      * @return {!Element}
    318      */
    319     _createViewSourceToggle: function(viewSource, handler)
    320     {
    321         var viewSourceToggleTitle = viewSource ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source");
    322         var viewSourceToggleButton = this._createToggleButton(viewSourceToggleTitle);
    323         viewSourceToggleButton.addEventListener("click", handler, false);
    324         return viewSourceToggleButton;
    325     },
    326 
    327     /**
    328      * @param {!Event} event
    329      */
    330     _toggleURLDecoding: function(event)
    331     {
    332         this._decodeRequestParameters = !this._decodeRequestParameters;
    333         this._refreshQueryString();
    334         this._refreshFormData();
    335     },
    336 
    337     _refreshRequestHeaders: function()
    338     {
    339         var treeElement = this._requestHeadersTreeElement;
    340 
    341         var headers = this._request.requestHeaders();
    342         headers = headers.slice();
    343         headers.sort(function(a, b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()) });
    344         var headersText = this._request.requestHeadersText();
    345 
    346         if (this._showRequestHeadersText && headersText)
    347             this._refreshHeadersText(WebInspector.UIString("Request Headers"), headers.length, headersText, treeElement);
    348         else
    349             this._refreshHeaders(WebInspector.UIString("Request Headers"), headers, treeElement, headersText === undefined);
    350 
    351         if (headersText) {
    352             var toggleButton = this._createHeadersToggleButton(this._showRequestHeadersText);
    353             toggleButton.addEventListener("click", this._toggleRequestHeadersText.bind(this), false);
    354             treeElement.listItemElement.appendChild(toggleButton);
    355         }
    356 
    357         this._refreshFormData();
    358     },
    359 
    360     _refreshResponseHeaders: function()
    361     {
    362         var treeElement = this._responseHeadersTreeElement;
    363         var headers = this._request.sortedResponseHeaders;
    364         var headersText = this._request.responseHeadersText;
    365 
    366         if (this._showResponseHeadersText)
    367             this._refreshHeadersText(WebInspector.UIString("Response Headers"), headers.length, headersText, treeElement);
    368         else
    369             this._refreshHeaders(WebInspector.UIString("Response Headers"), headers, treeElement);
    370 
    371         if (headersText) {
    372             var toggleButton = this._createHeadersToggleButton(this._showResponseHeadersText);
    373             toggleButton.addEventListener("click", this._toggleResponseHeadersText.bind(this), false);
    374             treeElement.listItemElement.appendChild(toggleButton);
    375         }
    376     },
    377 
    378     _refreshHTTPInformation: function()
    379     {
    380         var requestMethodElement = this._requestMethodTreeElement;
    381         requestMethodElement.hidden = !this._request.statusCode;
    382         var statusCodeElement = this._statusCodeTreeElement;
    383         statusCodeElement.hidden = !this._request.statusCode;
    384 
    385         if (this._request.statusCode) {
    386             var statusCodeFragment = document.createDocumentFragment();
    387             statusCodeFragment.createChild("div", "header-name").textContent = WebInspector.UIString("Status Code") + ":";
    388 
    389             var statusCodeImage = statusCodeFragment.createChild("div", "resource-status-image");
    390             statusCodeImage.title = this._request.statusCode + " " + this._request.statusText;
    391 
    392             if (this._request.statusCode < 300 || this._request.statusCode === 304)
    393                 statusCodeImage.classList.add("green-ball");
    394             else if (this._request.statusCode < 400)
    395                 statusCodeImage.classList.add("orange-ball");
    396             else
    397                 statusCodeImage.classList.add("red-ball");
    398 
    399             requestMethodElement.title = this._formatHeader(WebInspector.UIString("Request Method"), this._request.requestMethod);
    400 
    401             var statusTextElement = statusCodeFragment.createChild("div", "header-value source-code");
    402             var statusText = this._request.statusCode + " " + this._request.statusText;
    403             if (this._request.fetchedViaServiceWorker) {
    404                 statusText += " " + WebInspector.UIString("(from ServiceWorker)");
    405                 statusTextElement.classList.add("status-from-cache");
    406             } else if (this._request.cached) {
    407                 statusText += " " + WebInspector.UIString("(from cache)");
    408                 statusTextElement.classList.add("status-from-cache");
    409             }
    410             statusTextElement.textContent = statusText;
    411 
    412             statusCodeElement.title = statusCodeFragment;
    413         }
    414     },
    415 
    416     /**
    417      * @param {string} title
    418      * @param {!TreeElement} headersTreeElement
    419      * @param {number} headersLength
    420      */
    421     _refreshHeadersTitle: function(title, headersTreeElement, headersLength)
    422     {
    423         headersTreeElement.listItemElement.removeChildren();
    424         headersTreeElement.listItemElement.createTextChild(title);
    425 
    426         var headerCount = WebInspector.UIString(" (%d)", headersLength);
    427         headersTreeElement.listItemElement.createChild("span", "header-count").textContent = headerCount;
    428     },
    429 
    430     /**
    431      * @param {string} title
    432      * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
    433      * @param {!TreeElement} headersTreeElement
    434      * @param {boolean=} provisionalHeaders
    435      */
    436     _refreshHeaders: function(title, headers, headersTreeElement, provisionalHeaders)
    437     {
    438         headersTreeElement.removeChildren();
    439 
    440         var length = headers.length;
    441         this._refreshHeadersTitle(title, headersTreeElement, length);
    442 
    443         if (provisionalHeaders) {
    444             var cautionText = WebInspector.UIString("Provisional headers are shown");
    445             var cautionFragment = document.createDocumentFragment();
    446             cautionFragment.createChild("div", "warning-icon-small");
    447             cautionFragment.createChild("div", "caution").textContent = cautionText;
    448             var cautionTreeElement = new TreeElement(cautionFragment);
    449             cautionTreeElement.selectable = false;
    450             headersTreeElement.appendChild(cautionTreeElement);
    451         }
    452 
    453         headersTreeElement.hidden = !length && !provisionalHeaders;
    454         for (var i = 0; i < length; ++i) {
    455             var headerTreeElement = new TreeElement(this._formatHeader(headers[i].name, headers[i].value));
    456             headerTreeElement.selectable = false;
    457             headersTreeElement.appendChild(headerTreeElement);
    458         }
    459     },
    460 
    461     /**
    462      * @param {string} title
    463      * @param {number} count
    464      * @param {string} headersText
    465      * @param {!TreeElement} headersTreeElement
    466      */
    467     _refreshHeadersText: function(title, count, headersText, headersTreeElement)
    468     {
    469         this._populateTreeElementWithSourceText(headersTreeElement, headersText);
    470         this._refreshHeadersTitle(title, headersTreeElement, count);
    471     },
    472 
    473     _refreshRemoteAddress: function()
    474     {
    475         var remoteAddress = this._request.remoteAddress();
    476         var treeElement = this._remoteAddressTreeElement;
    477         treeElement.hidden = !remoteAddress;
    478         if (remoteAddress)
    479             treeElement.title = this._formatHeader(WebInspector.UIString("Remote Address"), remoteAddress);
    480     },
    481 
    482     /**
    483      * @param {!Event} event
    484      */
    485     _toggleRequestHeadersText: function(event)
    486     {
    487         this._showRequestHeadersText = !this._showRequestHeadersText;
    488         this._refreshRequestHeaders();
    489     },
    490 
    491     /**
    492      * @param {!Event} event
    493      */
    494     _toggleResponseHeadersText: function(event)
    495     {
    496         this._showResponseHeadersText = !this._showResponseHeadersText;
    497         this._refreshResponseHeaders();
    498     },
    499 
    500     /**
    501      * @param {string} title
    502      * @return {!Element}
    503      */
    504     _createToggleButton: function(title)
    505     {
    506         var button = document.createElement("span");
    507         button.classList.add("header-toggle");
    508         button.textContent = title;
    509         return button;
    510     },
    511 
    512     /**
    513      * @param {boolean} isHeadersTextShown
    514      * @return {!Element}
    515      */
    516     _createHeadersToggleButton: function(isHeadersTextShown)
    517     {
    518         var toggleTitle = isHeadersTextShown ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source");
    519         return this._createToggleButton(toggleTitle);
    520     },
    521 
    522     __proto__: WebInspector.VBox.prototype
    523 }
    524