blob: 69e80ec6f168221bfe6b3de46d82ee624466ae18 [file] [log] [blame]
/*
* Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
* Copyright (C) IBM Corp. 2009 All rights reserved.
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @constructor
* @extends {WebInspector.VBox}
* @param {!WebInspector.NetworkRequest} request
*/
WebInspector.RequestHeadersView = function(request)
{
WebInspector.VBox.call(this);
this.registerRequiredCSS("network/requestHeadersView.css");
this.element.classList.add("request-headers-view");
this._request = request;
this._decodeRequestParameters = true;
this._showRequestHeadersText = false;
this._showResponseHeadersText = false;
this._requestHeaderFilterSetting = WebInspector.settings.createSetting("requestHeaderFilterSetting", "");
/** @type {?RegExp} */
this._filterRegex = null;
if (Runtime.experiments.isEnabled("networkRequestHeadersFilterInDetailsView")) {
this._filterInput = this.element.createChild("input", "filter-input");
this._filterInput.type = "text";
this._filterInput.placeholder = WebInspector.UIString("Filter headers");
this._filterInput.addEventListener("input", this._updateFilter.bind(this), false);
this._filterInput.addEventListener("keydown", this._onFilterKeyDown.bind(this), false);
this._filterInput.value = this._requestHeaderFilterSetting.get() || "";
}
var root = new TreeOutline(true);
root.element.classList.add("outline-disclosure");
root.expandTreeElementsWhenArrowing = true;
this.element.appendChild(root.element);
var generalCategory = new WebInspector.RequestHeadersView.Category(root, "general", WebInspector.UIString("General"));
generalCategory.hidden = false;
this._urlItem = generalCategory.createLeaf();
this._requestMethodItem = generalCategory.createLeaf();
this._statusCodeItem = generalCategory.createLeaf();
this._remoteAddressItem = generalCategory.createLeaf();
this._remoteAddressItem.hidden = true;
this._responseHeadersCategory = new WebInspector.RequestHeadersView.Category(root, "responseHeaders", "");
this._requestHeadersCategory = new WebInspector.RequestHeadersView.Category(root, "requestHeaders", "");
this._queryStringCategory = new WebInspector.RequestHeadersView.Category(root, "queryString", "");
this._formDataCategory = new WebInspector.RequestHeadersView.Category(root, "formData", "");
this._requestPayloadCategory = new WebInspector.RequestHeadersView.Category(root, "requestPayload", WebInspector.UIString("Request Payload"));
if (Runtime.experiments.isEnabled("networkRequestHeadersFilterInDetailsView")) {
this._updateFilter();
}
}
WebInspector.RequestHeadersView.prototype = {
_updateFilter: function()
{
var text = this._filterInput.value;
this._requestHeaderFilterSetting.set(text);
this._filterRegex = text ? new RegExp(text.escapeForRegExp(), "i") : null;
this._updateHeaders();
},
/**
* @param {!Event} event
*/
_onFilterKeyDown: function(event)
{
var text = this._filterInput.value;
if (!text)
return;
if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B") {
event.consume(true);
this._filterInput.value = "";
this._updateFilter();
}
},
_updateHeaders: function()
{
this._refreshRequestHeaders();
this._refreshResponseHeaders();
},
wasShown: function()
{
this._request.addEventListener(WebInspector.NetworkRequest.Events.RemoteAddressChanged, this._refreshRemoteAddress, this);
this._request.addEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
this._request.addEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
this._request.addEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
this._refreshURL();
this._refreshQueryString();
this._updateHeaders();
this._refreshHTTPInformation();
this._refreshRemoteAddress();
},
willHide: function()
{
this._request.removeEventListener(WebInspector.NetworkRequest.Events.RemoteAddressChanged, this._refreshRemoteAddress, this);
this._request.removeEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
this._request.removeEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
this._request.removeEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
},
/**
* @param {string} name
* @param {string} value
* @return {!DocumentFragment}
*/
_formatHeader: function(name, value)
{
var fragment = createDocumentFragment();
fragment.createChild("div", "header-name").textContent = name + ":";
fragment.createChild("div", "header-value source-code").textContent = value;
return fragment;
},
/**
* @param {string} value
* @param {string} className
* @param {boolean} decodeParameters
* @return {!Element}
*/
_formatParameter: function(value, className, decodeParameters)
{
var errorDecoding = false;
if (decodeParameters) {
value = value.replace(/\+/g, " ");
if (value.indexOf("%") >= 0) {
try {
value = decodeURIComponent(value);
} catch (e) {
errorDecoding = true;
}
}
}
var div = createElementWithClass("div", className);
if (errorDecoding)
div.createChild("span", "error-message").textContent = WebInspector.UIString("(unable to decode value)");
else
div.textContent = value;
return div;
},
_refreshURL: function()
{
this._urlItem.title = this._formatHeader(WebInspector.UIString("Request URL"), this._request.url);
},
_refreshQueryString: function()
{
var queryString = this._request.queryString();
var queryParameters = this._request.queryParameters;
this._queryStringCategory.hidden = !queryParameters;
if (queryParameters)
this._refreshParams(WebInspector.UIString("Query String Parameters"), queryParameters, queryString, this._queryStringCategory);
},
_refreshFormData: function()
{
this._formDataCategory.hidden = true;
this._requestPayloadCategory.hidden = true;
var formData = this._request.requestFormData;
if (!formData)
return;
var formParameters = this._request.formParameters;
if (formParameters) {
this._formDataCategory.hidden = false;
this._refreshParams(WebInspector.UIString("Form Data"), formParameters, formData, this._formDataCategory);
} else {
this._requestPayloadCategory.hidden = false;
try {
var json = JSON.parse(formData);
this._refreshRequestJSONPayload(json, formData);
} catch (e) {
this._populateTreeElementWithSourceText(this._requestPayloadCategory, formData);
}
}
},
/**
* @param {!TreeElement} treeElement
* @param {?string} sourceText
*/
_populateTreeElementWithSourceText: function(treeElement, sourceText)
{
var sourceTextElement = createElementWithClass("span", "header-value source-code");
sourceTextElement.textContent = String(sourceText || "").trim();
var sourceTreeElement = new TreeElement(sourceTextElement);
sourceTreeElement.selectable = false;
treeElement.removeChildren();
treeElement.appendChild(sourceTreeElement);
},
/**
* @param {string} title
* @param {?Array.<!WebInspector.NetworkRequest.NameValue>} params
* @param {?string} sourceText
* @param {!TreeElement} paramsTreeElement
*/
_refreshParams: function(title, params, sourceText, paramsTreeElement)
{
paramsTreeElement.removeChildren();
paramsTreeElement.listItemElement.removeChildren();
paramsTreeElement.listItemElement.createTextChild(title);
var headerCount = createElementWithClass("span", "header-count");
headerCount.textContent = WebInspector.UIString("\u00A0(%d)", params.length);
paramsTreeElement.listItemElement.appendChild(headerCount);
/**
* @param {!Event} event
* @this {WebInspector.RequestHeadersView}
*/
function toggleViewSource(event)
{
paramsTreeElement._viewSource = !paramsTreeElement._viewSource;
this._refreshParams(title, params, sourceText, paramsTreeElement);
event.consume();
}
paramsTreeElement.listItemElement.appendChild(this._createViewSourceToggle(paramsTreeElement._viewSource, toggleViewSource.bind(this)));
if (paramsTreeElement._viewSource) {
this._populateTreeElementWithSourceText(paramsTreeElement, sourceText);
return;
}
var toggleTitle = this._decodeRequestParameters ? WebInspector.UIString("view URL encoded") : WebInspector.UIString("view decoded");
var toggleButton = this._createToggleButton(toggleTitle);
toggleButton.addEventListener("click", this._toggleURLDecoding.bind(this), false);
paramsTreeElement.listItemElement.appendChild(toggleButton);
for (var i = 0; i < params.length; ++i) {
var paramNameValue = createDocumentFragment();
var name = this._formatParameter(params[i].name + ":", "header-name", this._decodeRequestParameters);
var value = this._formatParameter(params[i].value, "header-value source-code", this._decodeRequestParameters);
paramNameValue.appendChild(name);
paramNameValue.appendChild(value);
var parmTreeElement = new TreeElement(paramNameValue);
parmTreeElement.selectable = false;
paramsTreeElement.appendChild(parmTreeElement);
}
},
/**
* @param {*} parsedObject
* @param {string} sourceText
*/
_refreshRequestJSONPayload: function(parsedObject, sourceText)
{
var treeElement = this._requestPayloadCategory;
treeElement.removeChildren();
var listItem = this._requestPayloadCategory.listItemElement;
listItem.removeChildren();
listItem.createTextChild(this._requestPayloadCategory.title);
/**
* @param {!Event} event
* @this {WebInspector.RequestHeadersView}
*/
function toggleViewSource(event)
{
treeElement._viewSource = !treeElement._viewSource;
this._refreshRequestJSONPayload(parsedObject, sourceText);
event.consume();
}
listItem.appendChild(this._createViewSourceToggle(treeElement._viewSource, toggleViewSource.bind(this)));
if (treeElement._viewSource) {
this._populateTreeElementWithSourceText(this._requestPayloadCategory, sourceText);
} else {
var object = WebInspector.RemoteObject.fromLocalObject(parsedObject);
var section = new WebInspector.ObjectPropertiesSection(object, object.description);
section.expand();
section.editable = false;
treeElement.appendChild(new TreeElement(section.element));
}
},
/**
* @param {boolean} viewSource
* @param {function(!Event)} handler
* @return {!Element}
*/
_createViewSourceToggle: function(viewSource, handler)
{
var viewSourceToggleTitle = viewSource ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source");
var viewSourceToggleButton = this._createToggleButton(viewSourceToggleTitle);
viewSourceToggleButton.addEventListener("click", handler, false);
return viewSourceToggleButton;
},
/**
* @param {!Event} event
*/
_toggleURLDecoding: function(event)
{
this._decodeRequestParameters = !this._decodeRequestParameters;
this._refreshQueryString();
this._refreshFormData();
event.consume();
},
_refreshRequestHeaders: function()
{
var treeElement = this._requestHeadersCategory;
var headers = this._request.requestHeaders().slice();
var filterRegex = this._filterRegex;
if (filterRegex)
headers = headers.filter(function(header) { return filterRegex.test(header.name) || filterRegex.test(header.value);});
headers.sort(function(a, b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); });
var headersText = this._request.requestHeadersText();
if (this._showRequestHeadersText && headersText)
this._refreshHeadersText(WebInspector.UIString("Request Headers"), headers.length, headersText, treeElement);
else
this._refreshHeaders(WebInspector.UIString("Request Headers"), headers, treeElement, headersText === undefined);
if (headersText) {
var toggleButton = this._createHeadersToggleButton(this._showRequestHeadersText);
toggleButton.addEventListener("click", this._toggleRequestHeadersText.bind(this), false);
treeElement.listItemElement.appendChild(toggleButton);
}
this._refreshFormData();
},
_refreshResponseHeaders: function()
{
var treeElement = this._responseHeadersCategory;
var headers = this._request.sortedResponseHeaders.slice();
var filterRegex = this._filterRegex;
if (filterRegex)
headers = headers.filter(function(header) { return filterRegex.test(header.name) || filterRegex.test(header.value);});
var headersText = this._request.responseHeadersText;
if (this._showResponseHeadersText)
this._refreshHeadersText(WebInspector.UIString("Response Headers"), headers.length, headersText, treeElement);
else
this._refreshHeaders(WebInspector.UIString("Response Headers"), headers, treeElement);
if (headersText) {
var toggleButton = this._createHeadersToggleButton(this._showResponseHeadersText);
toggleButton.addEventListener("click", this._toggleResponseHeadersText.bind(this), false);
treeElement.listItemElement.appendChild(toggleButton);
}
},
_refreshHTTPInformation: function()
{
var requestMethodElement = this._requestMethodItem;
requestMethodElement.hidden = !this._request.statusCode;
var statusCodeElement = this._statusCodeItem;
statusCodeElement.hidden = !this._request.statusCode;
if (this._request.statusCode) {
var statusCodeFragment = createDocumentFragment();
statusCodeFragment.createChild("div", "header-name").textContent = WebInspector.UIString("Status Code") + ":";
var statusCodeImage = statusCodeFragment.createChild("label", "resource-status-image", "dt-icon-label");
statusCodeImage.title = this._request.statusCode + " " + this._request.statusText;
if (this._request.statusCode < 300 || this._request.statusCode === 304)
statusCodeImage.type = "green-ball";
else if (this._request.statusCode < 400)
statusCodeImage.type = "orange-ball";
else
statusCodeImage.type = "red-ball";
requestMethodElement.title = this._formatHeader(WebInspector.UIString("Request Method"), this._request.requestMethod);
var statusTextElement = statusCodeFragment.createChild("div", "header-value source-code");
var statusText = this._request.statusCode + " " + this._request.statusText;
if (this._request.fetchedViaServiceWorker) {
statusText += " " + WebInspector.UIString("(from ServiceWorker)");
statusTextElement.classList.add("status-from-cache");
} else if (this._request.cached()) {
statusText += " " + WebInspector.UIString("(from cache)");
statusTextElement.classList.add("status-from-cache");
}
statusTextElement.textContent = statusText;
statusCodeElement.title = statusCodeFragment;
}
},
/**
* @param {string} title
* @param {!TreeElement} headersTreeElement
* @param {number} headersLength
*/
_refreshHeadersTitle: function(title, headersTreeElement, headersLength)
{
headersTreeElement.listItemElement.removeChildren();
headersTreeElement.listItemElement.createTextChild(title);
var headerCount = WebInspector.UIString("\u00A0(%d)", headersLength);
headersTreeElement.listItemElement.createChild("span", "header-count").textContent = headerCount;
},
/**
* @param {string} title
* @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
* @param {!TreeElement} headersTreeElement
* @param {boolean=} provisionalHeaders
*/
_refreshHeaders: function(title, headers, headersTreeElement, provisionalHeaders)
{
headersTreeElement.removeChildren();
var length = headers.length;
this._refreshHeadersTitle(title, headersTreeElement, length);
if (provisionalHeaders) {
var cautionText = WebInspector.UIString("Provisional headers are shown");
var cautionFragment = createDocumentFragment();
cautionFragment.createChild("label", "", "dt-icon-label").type = "warning-icon";
cautionFragment.createChild("div", "caution").textContent = cautionText;
var cautionTreeElement = new TreeElement(cautionFragment);
cautionTreeElement.selectable = false;
headersTreeElement.appendChild(cautionTreeElement);
}
headersTreeElement.hidden = !length && !provisionalHeaders;
for (var i = 0; i < length; ++i) {
var headerTreeElement = new TreeElement(this._formatHeader(headers[i].name, headers[i].value));
headerTreeElement.selectable = false;
headersTreeElement.appendChild(headerTreeElement);
}
},
/**
* @param {string} title
* @param {number} count
* @param {string} headersText
* @param {!TreeElement} headersTreeElement
*/
_refreshHeadersText: function(title, count, headersText, headersTreeElement)
{
this._populateTreeElementWithSourceText(headersTreeElement, headersText);
this._refreshHeadersTitle(title, headersTreeElement, count);
},
_refreshRemoteAddress: function()
{
var remoteAddress = this._request.remoteAddress();
var treeElement = this._remoteAddressItem;
treeElement.hidden = !remoteAddress;
if (remoteAddress)
treeElement.title = this._formatHeader(WebInspector.UIString("Remote Address"), remoteAddress);
},
/**
* @param {!Event} event
*/
_toggleRequestHeadersText: function(event)
{
this._showRequestHeadersText = !this._showRequestHeadersText;
this._refreshRequestHeaders();
event.consume();
},
/**
* @param {!Event} event
*/
_toggleResponseHeadersText: function(event)
{
this._showResponseHeadersText = !this._showResponseHeadersText;
this._refreshResponseHeaders();
event.consume();
},
/**
* @param {string} title
* @return {!Element}
*/
_createToggleButton: function(title)
{
var button = createElementWithClass("span", "header-toggle");
button.textContent = title;
return button;
},
/**
* @param {boolean} isHeadersTextShown
* @return {!Element}
*/
_createHeadersToggleButton: function(isHeadersTextShown)
{
var toggleTitle = isHeadersTextShown ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source");
return this._createToggleButton(toggleTitle);
},
__proto__: WebInspector.VBox.prototype
}
/**
* @constructor
* @extends {TreeElement}
* @param {!TreeOutline} root
* @param {string} name
* @param {string=} title
*/
WebInspector.RequestHeadersView.Category = function(root, name, title)
{
TreeElement.call(this, title || "", true);
this.selectable = false;
this.toggleOnClick = true;
this.hidden = true;
this._expandedSetting = WebInspector.settings.createSetting("request-info-" + name + "-category-expanded", true);
this.expanded = this._expandedSetting.get();
root.appendChild(this);
}
WebInspector.RequestHeadersView.Category.prototype = {
/**
* @return {!TreeElement}
*/
createLeaf: function()
{
var leaf = new TreeElement();
leaf.selectable = false;
this.appendChild(leaf);
return leaf;
},
onexpand: function()
{
this._expandedSetting.set(true);
},
oncollapse: function()
{
this._expandedSetting.set(false);
},
__proto__: TreeElement.prototype
}