michael@0: /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0:
michael@0: "use strict";
michael@0:
michael@0: const {Cc, Ci, Cu} = require("chrome");
michael@0:
michael@0: loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
michael@0: loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
michael@0: loader.lazyServiceGetter(this, "mimeService", "@mozilla.org/mime;1", "nsIMIMEService");
michael@0:
michael@0: let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
michael@0:
michael@0: const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
michael@0: let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
michael@0:
michael@0:
michael@0: /**
michael@0: * Creates a new NetworkPanel.
michael@0: *
michael@0: * @constructor
michael@0: * @param nsIDOMNode aParent
michael@0: * Parent node to append the created panel to.
michael@0: * @param object aHttpActivity
michael@0: * HttpActivity to display in the panel.
michael@0: * @param object aWebConsoleFrame
michael@0: * The parent WebConsoleFrame object that owns this network panel
michael@0: * instance.
michael@0: */
michael@0: function NetworkPanel(aParent, aHttpActivity, aWebConsoleFrame)
michael@0: {
michael@0: let doc = aParent.ownerDocument;
michael@0: this.httpActivity = aHttpActivity;
michael@0: this.webconsole = aWebConsoleFrame;
michael@0: this._longStringClick = this._longStringClick.bind(this);
michael@0: this._responseBodyFetch = this._responseBodyFetch.bind(this);
michael@0: this._requestBodyFetch = this._requestBodyFetch.bind(this);
michael@0:
michael@0: // Create the underlaying panel
michael@0: this.panel = createElement(doc, "panel", {
michael@0: label: l10n.getStr("NetworkPanel.label"),
michael@0: titlebar: "normal",
michael@0: noautofocus: "true",
michael@0: noautohide: "true",
michael@0: close: "true"
michael@0: });
michael@0:
michael@0: // Create the iframe that displays the NetworkPanel XHTML.
michael@0: this.iframe = createAndAppendElement(this.panel, "iframe", {
michael@0: src: "chrome://browser/content/devtools/NetworkPanel.xhtml",
michael@0: type: "content",
michael@0: flex: "1"
michael@0: });
michael@0:
michael@0: let self = this;
michael@0:
michael@0: // Destroy the panel when it's closed.
michael@0: this.panel.addEventListener("popuphidden", function onPopupHide() {
michael@0: self.panel.removeEventListener("popuphidden", onPopupHide, false);
michael@0: self.panel.parentNode.removeChild(self.panel);
michael@0: self.panel = null;
michael@0: self.iframe = null;
michael@0: self.httpActivity = null;
michael@0: self.webconsole = null;
michael@0:
michael@0: if (self.linkNode) {
michael@0: self.linkNode._panelOpen = false;
michael@0: self.linkNode = null;
michael@0: }
michael@0: }, false);
michael@0:
michael@0: // Set the document object and update the content once the panel is loaded.
michael@0: this.iframe.addEventListener("load", function onLoad() {
michael@0: if (!self.iframe) {
michael@0: return;
michael@0: }
michael@0:
michael@0: self.iframe.removeEventListener("load", onLoad, true);
michael@0: self.update();
michael@0: }, true);
michael@0:
michael@0: this.panel.addEventListener("popupshown", function onPopupShown() {
michael@0: self.panel.removeEventListener("popupshown", onPopupShown, true);
michael@0: self.update();
michael@0: }, true);
michael@0:
michael@0: // Create the footer.
michael@0: let footer = createElement(doc, "hbox", { align: "end" });
michael@0: createAndAppendElement(footer, "spacer", { flex: 1 });
michael@0:
michael@0: createAndAppendElement(footer, "resizer", { dir: "bottomend" });
michael@0: this.panel.appendChild(footer);
michael@0:
michael@0: aParent.appendChild(this.panel);
michael@0: }
michael@0: exports.NetworkPanel = NetworkPanel;
michael@0:
michael@0: NetworkPanel.prototype =
michael@0: {
michael@0: /**
michael@0: * The current state of the output.
michael@0: */
michael@0: _state: 0,
michael@0:
michael@0: /**
michael@0: * State variables.
michael@0: */
michael@0: _INIT: 0,
michael@0: _DISPLAYED_REQUEST_HEADER: 1,
michael@0: _DISPLAYED_REQUEST_BODY: 2,
michael@0: _DISPLAYED_RESPONSE_HEADER: 3,
michael@0: _TRANSITION_CLOSED: 4,
michael@0:
michael@0: _fromDataRegExp: /Content-Type\:\s*application\/x-www-form-urlencoded/,
michael@0:
michael@0: _contentType: null,
michael@0:
michael@0: /**
michael@0: * Function callback invoked whenever the panel content is updated. This is
michael@0: * used only by tests.
michael@0: *
michael@0: * @private
michael@0: * @type function
michael@0: */
michael@0: _onUpdate: null,
michael@0:
michael@0: get document() {
michael@0: return this.iframe && this.iframe.contentWindow ?
michael@0: this.iframe.contentWindow.document : null;
michael@0: },
michael@0:
michael@0: /**
michael@0: * Small helper function that is nearly equal to l10n.getFormatStr
michael@0: * except that it prefixes aName with "NetworkPanel.".
michael@0: *
michael@0: * @param string aName
michael@0: * The name of an i10n string to format. This string is prefixed with
michael@0: * "NetworkPanel." before calling the HUDService.getFormatStr function.
michael@0: * @param array aArray
michael@0: * Values used as placeholder for the i10n string.
michael@0: * @returns string
michael@0: * The i10n formated string.
michael@0: */
michael@0: _format: function NP_format(aName, aArray)
michael@0: {
michael@0: return l10n.getFormatStr("NetworkPanel." + aName, aArray);
michael@0: },
michael@0:
michael@0: /**
michael@0: * Returns the content type of the response body. This is based on the
michael@0: * response.content.mimeType property. If this value is not available, then
michael@0: * the content type is guessed by the file extension of the request URL.
michael@0: *
michael@0: * @return string
michael@0: * Content type or empty string if no content type could be figured
michael@0: * out.
michael@0: */
michael@0: get contentType()
michael@0: {
michael@0: if (this._contentType) {
michael@0: return this._contentType;
michael@0: }
michael@0:
michael@0: let request = this.httpActivity.request;
michael@0: let response = this.httpActivity.response;
michael@0:
michael@0: let contentType = "";
michael@0: let types = response.content ?
michael@0: (response.content.mimeType || "").split(/,|;/) : [];
michael@0: for (let i = 0; i < types.length; i++) {
michael@0: if (types[i] in NetworkHelper.mimeCategoryMap) {
michael@0: contentType = types[i];
michael@0: break;
michael@0: }
michael@0: }
michael@0:
michael@0: if (contentType) {
michael@0: this._contentType = contentType;
michael@0: return contentType;
michael@0: }
michael@0:
michael@0: // Try to get the content type from the request file extension.
michael@0: let uri = NetUtil.newURI(request.url);
michael@0: if ((uri instanceof Ci.nsIURL) && uri.fileExtension) {
michael@0: try {
michael@0: contentType = mimeService.getTypeFromExtension(uri.fileExtension);
michael@0: }
michael@0: catch(ex) {
michael@0: // Added to prevent failures on OS X 64. No Flash?
michael@0: Cu.reportError(ex);
michael@0: }
michael@0: }
michael@0:
michael@0: this._contentType = contentType;
michael@0: return contentType;
michael@0: },
michael@0:
michael@0: /**
michael@0: *
michael@0: * @returns boolean
michael@0: * True if the response is an image, false otherwise.
michael@0: */
michael@0: get _responseIsImage()
michael@0: {
michael@0: return this.contentType &&
michael@0: NetworkHelper.mimeCategoryMap[this.contentType] == "image";
michael@0: },
michael@0:
michael@0: /**
michael@0: *
michael@0: * @returns boolean
michael@0: * True if the response body contains text, false otherwise.
michael@0: */
michael@0: get _isResponseBodyTextData()
michael@0: {
michael@0: return this.contentType ?
michael@0: NetworkHelper.isTextMimeType(this.contentType) : false;
michael@0: },
michael@0:
michael@0: /**
michael@0: * Tells if the server response is cached.
michael@0: *
michael@0: * @returns boolean
michael@0: * Returns true if the server responded that the request is already
michael@0: * in the browser's cache, false otherwise.
michael@0: */
michael@0: get _isResponseCached()
michael@0: {
michael@0: return this.httpActivity.response.status == 304;
michael@0: },
michael@0:
michael@0: /**
michael@0: * Tells if the request body includes form data.
michael@0: *
michael@0: * @returns boolean
michael@0: * Returns true if the posted body contains form data.
michael@0: */
michael@0: get _isRequestBodyFormData()
michael@0: {
michael@0: let requestBody = this.httpActivity.request.postData.text;
michael@0: if (typeof requestBody == "object" && requestBody.type == "longString") {
michael@0: requestBody = requestBody.initial;
michael@0: }
michael@0: return this._fromDataRegExp.test(requestBody);
michael@0: },
michael@0:
michael@0: /**
michael@0: * Appends the node with id=aId by the text aValue.
michael@0: *
michael@0: * @private
michael@0: * @param string aId
michael@0: * @param string aValue
michael@0: * @return nsIDOMElement
michael@0: * The DOM element with id=aId.
michael@0: */
michael@0: _appendTextNode: function NP__appendTextNode(aId, aValue)
michael@0: {
michael@0: let textNode = this.document.createTextNode(aValue);
michael@0: let elem = this.document.getElementById(aId);
michael@0: elem.appendChild(textNode);
michael@0: return elem;
michael@0: },
michael@0:
michael@0: /**
michael@0: * Generates some HTML to display the key-value pair of the aList data. The
michael@0: * generated HTML is added to node with id=aParentId.
michael@0: *
michael@0: * @param string aParentId
michael@0: * Id of the parent node to append the list to.
michael@0: * @oaram array aList
michael@0: * Array that holds the objects you want to display. Each object must
michael@0: * have two properties: name and value.
michael@0: * @param boolean aIgnoreCookie
michael@0: * If true, the key-value named "Cookie" is not added to the list.
michael@0: * @returns void
michael@0: */
michael@0: _appendList: function NP_appendList(aParentId, aList, aIgnoreCookie)
michael@0: {
michael@0: let parent = this.document.getElementById(aParentId);
michael@0: let doc = this.document;
michael@0:
michael@0: aList.sort(function(a, b) {
michael@0: return a.name.toLowerCase() < b.name.toLowerCase();
michael@0: });
michael@0:
michael@0: aList.forEach(function(aItem) {
michael@0: let name = aItem.name;
michael@0: if (aIgnoreCookie && (name == "Cookie" || name == "Set-Cookie")) {
michael@0: return;
michael@0: }
michael@0:
michael@0: let value = aItem.value;
michael@0: let longString = null;
michael@0: if (typeof value == "object" && value.type == "longString") {
michael@0: value = value.initial;
michael@0: longString = true;
michael@0: }
michael@0:
michael@0: /**
michael@0: * The following code creates the HTML:
michael@0: *
michael@0: * ${line}: |
michael@0: * ${aList[line]} |
michael@0: *
michael@0: * and adds it to parent.
michael@0: */
michael@0: let row = doc.createElement("tr");
michael@0: let textNode = doc.createTextNode(name + ":");
michael@0: let th = doc.createElement("th");
michael@0: th.setAttribute("scope", "row");
michael@0: th.setAttribute("class", "property-name");
michael@0: th.appendChild(textNode);
michael@0: row.appendChild(th);
michael@0:
michael@0: textNode = doc.createTextNode(value);
michael@0: let td = doc.createElement("td");
michael@0: td.setAttribute("class", "property-value");
michael@0: td.appendChild(textNode);
michael@0:
michael@0: if (longString) {
michael@0: let a = doc.createElement("a");
michael@0: a.href = "#";
michael@0: a.className = "longStringEllipsis";
michael@0: a.addEventListener("mousedown", this._longStringClick.bind(this, aItem));
michael@0: a.textContent = l10n.getStr("longStringEllipsis");
michael@0: td.appendChild(a);
michael@0: }
michael@0:
michael@0: row.appendChild(td);
michael@0:
michael@0: parent.appendChild(row);
michael@0: }.bind(this));
michael@0: },
michael@0:
michael@0: /**
michael@0: * The click event handler for the ellipsis which allows the user to retrieve
michael@0: * the full header value.
michael@0: *
michael@0: * @private
michael@0: * @param object aHeader
michael@0: * The header object with the |name| and |value| properties.
michael@0: * @param nsIDOMEvent aEvent
michael@0: * The DOM click event object.
michael@0: */
michael@0: _longStringClick: function NP__longStringClick(aHeader, aEvent)
michael@0: {
michael@0: aEvent.preventDefault();
michael@0:
michael@0: let longString = this.webconsole.webConsoleClient.longString(aHeader.value);
michael@0:
michael@0: longString.substring(longString.initial.length, longString.length,
michael@0: function NP__onLongStringSubstring(aResponse)
michael@0: {
michael@0: if (aResponse.error) {
michael@0: Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
michael@0: return;
michael@0: }
michael@0:
michael@0: aHeader.value = aHeader.value.initial + aResponse.substring;
michael@0:
michael@0: let textNode = aEvent.target.previousSibling;
michael@0: textNode.textContent += aResponse.substring;
michael@0: textNode.parentNode.removeChild(aEvent.target);
michael@0: });
michael@0: },
michael@0:
michael@0: /**
michael@0: * Displays the node with id=aId.
michael@0: *
michael@0: * @private
michael@0: * @param string aId
michael@0: * @return nsIDOMElement
michael@0: * The element with id=aId.
michael@0: */
michael@0: _displayNode: function NP__displayNode(aId)
michael@0: {
michael@0: let elem = this.document.getElementById(aId);
michael@0: elem.style.display = "block";
michael@0: },
michael@0:
michael@0: /**
michael@0: * Sets the request URL, request method, the timing information when the
michael@0: * request started and the request header content on the NetworkPanel.
michael@0: * If the request header contains cookie data, a list of sent cookies is
michael@0: * generated and a special sent cookie section is displayed + the cookie list
michael@0: * added to it.
michael@0: *
michael@0: * @returns void
michael@0: */
michael@0: _displayRequestHeader: function NP__displayRequestHeader()
michael@0: {
michael@0: let request = this.httpActivity.request;
michael@0: let requestTime = new Date(this.httpActivity.startedDateTime);
michael@0:
michael@0: this._appendTextNode("headUrl", request.url);
michael@0: this._appendTextNode("headMethod", request.method);
michael@0: this._appendTextNode("requestHeadersInfo",
michael@0: l10n.timestampString(requestTime));
michael@0:
michael@0: this._appendList("requestHeadersContent", request.headers, true);
michael@0:
michael@0: if (request.cookies.length > 0) {
michael@0: this._displayNode("requestCookie");
michael@0: this._appendList("requestCookieContent", request.cookies);
michael@0: }
michael@0: },
michael@0:
michael@0: /**
michael@0: * Displays the request body section of the NetworkPanel and set the request
michael@0: * body content on the NetworkPanel.
michael@0: *
michael@0: * @returns void
michael@0: */
michael@0: _displayRequestBody: function NP__displayRequestBody()
michael@0: {
michael@0: let postData = this.httpActivity.request.postData;
michael@0: this._displayNode("requestBody");
michael@0: this._appendTextNode("requestBodyContent", postData.text);
michael@0: },
michael@0:
michael@0: /*
michael@0: * Displays the `sent form data` section. Parses the request header for the
michael@0: * submitted form data displays it inside of the `sent form data` section.
michael@0: *
michael@0: * @returns void
michael@0: */
michael@0: _displayRequestForm: function NP__processRequestForm()
michael@0: {
michael@0: let postData = this.httpActivity.request.postData.text;
michael@0: let requestBodyLines = postData.split("\n");
michael@0: let formData = requestBodyLines[requestBodyLines.length - 1].
michael@0: replace(/\+/g, " ").split("&");
michael@0:
michael@0: function unescapeText(aText)
michael@0: {
michael@0: try {
michael@0: return decodeURIComponent(aText);
michael@0: }
michael@0: catch (ex) {
michael@0: return decodeURIComponent(unescape(aText));
michael@0: }
michael@0: }
michael@0:
michael@0: let formDataArray = [];
michael@0: for (let i = 0; i < formData.length; i++) {
michael@0: let data = formData[i];
michael@0: let idx = data.indexOf("=");
michael@0: let key = data.substring(0, idx);
michael@0: let value = data.substring(idx + 1);
michael@0: formDataArray.push({
michael@0: name: unescapeText(key),
michael@0: value: unescapeText(value)
michael@0: });
michael@0: }
michael@0:
michael@0: this._appendList("requestFormDataContent", formDataArray);
michael@0: this._displayNode("requestFormData");
michael@0: },
michael@0:
michael@0: /**
michael@0: * Displays the response section of the NetworkPanel, sets the response status,
michael@0: * the duration between the start of the request and the receiving of the
michael@0: * response header as well as the response header content on the the NetworkPanel.
michael@0: *
michael@0: * @returns void
michael@0: */
michael@0: _displayResponseHeader: function NP__displayResponseHeader()
michael@0: {
michael@0: let timing = this.httpActivity.timings;
michael@0: let response = this.httpActivity.response;
michael@0:
michael@0: this._appendTextNode("headStatus",
michael@0: [response.httpVersion, response.status,
michael@0: response.statusText].join(" "));
michael@0:
michael@0: // Calculate how much time it took from the request start, until the
michael@0: // response started to be received.
michael@0: let deltaDuration = 0;
michael@0: ["dns", "connect", "send", "wait"].forEach(function (aValue) {
michael@0: let ms = timing[aValue];
michael@0: if (ms > -1) {
michael@0: deltaDuration += ms;
michael@0: }
michael@0: });
michael@0:
michael@0: this._appendTextNode("responseHeadersInfo",
michael@0: this._format("durationMS", [deltaDuration]));
michael@0:
michael@0: this._displayNode("responseContainer");
michael@0: this._appendList("responseHeadersContent", response.headers, true);
michael@0:
michael@0: if (response.cookies.length > 0) {
michael@0: this._displayNode("responseCookie");
michael@0: this._appendList("responseCookieContent", response.cookies);
michael@0: }
michael@0: },
michael@0:
michael@0: /**
michael@0: * Displays the respones image section, sets the source of the image displayed
michael@0: * in the image response section to the request URL and the duration between
michael@0: * the receiving of the response header and the end of the request. Once the
michael@0: * image is loaded, the size of the requested image is set.
michael@0: *
michael@0: * @returns void
michael@0: */
michael@0: _displayResponseImage: function NP__displayResponseImage()
michael@0: {
michael@0: let self = this;
michael@0: let timing = this.httpActivity.timings;
michael@0: let request = this.httpActivity.request;
michael@0: let response = this.httpActivity.response;
michael@0: let cached = "";
michael@0:
michael@0: if (this._isResponseCached) {
michael@0: cached = "Cached";
michael@0: }
michael@0:
michael@0: let imageNode = this.document.getElementById("responseImage" +
michael@0: cached + "Node");
michael@0:
michael@0: let text = response.content.text;
michael@0: if (typeof text == "object" && text.type == "longString") {
michael@0: this._showResponseBodyFetchLink();
michael@0: }
michael@0: else {
michael@0: imageNode.setAttribute("src",
michael@0: "data:" + this.contentType + ";base64," + text);
michael@0: }
michael@0:
michael@0: // This function is called to set the imageInfo.
michael@0: function setImageInfo() {
michael@0: self._appendTextNode("responseImage" + cached + "Info",
michael@0: self._format("imageSizeDeltaDurationMS",
michael@0: [ imageNode.width, imageNode.height, timing.receive ]
michael@0: )
michael@0: );
michael@0: }
michael@0:
michael@0: // Check if the image is already loaded.
michael@0: if (imageNode.width != 0) {
michael@0: setImageInfo();
michael@0: }
michael@0: else {
michael@0: // Image is not loaded yet therefore add a load event.
michael@0: imageNode.addEventListener("load", function imageNodeLoad() {
michael@0: imageNode.removeEventListener("load", imageNodeLoad, false);
michael@0: setImageInfo();
michael@0: }, false);
michael@0: }
michael@0:
michael@0: this._displayNode("responseImage" + cached);
michael@0: },
michael@0:
michael@0: /**
michael@0: * Displays the response body section, sets the the duration between
michael@0: * the receiving of the response header and the end of the request as well as
michael@0: * the content of the response body on the NetworkPanel.
michael@0: *
michael@0: * @returns void
michael@0: */
michael@0: _displayResponseBody: function NP__displayResponseBody()
michael@0: {
michael@0: let timing = this.httpActivity.timings;
michael@0: let response = this.httpActivity.response;
michael@0: let cached = this._isResponseCached ? "Cached" : "";
michael@0:
michael@0: this._appendTextNode("responseBody" + cached + "Info",
michael@0: this._format("durationMS", [timing.receive]));
michael@0:
michael@0: this._displayNode("responseBody" + cached);
michael@0:
michael@0: let text = response.content.text;
michael@0: if (typeof text == "object") {
michael@0: text = text.initial;
michael@0: this._showResponseBodyFetchLink();
michael@0: }
michael@0:
michael@0: this._appendTextNode("responseBody" + cached + "Content", text);
michael@0: },
michael@0:
michael@0: /**
michael@0: * Show the "fetch response body" link.
michael@0: * @private
michael@0: */
michael@0: _showResponseBodyFetchLink: function NP__showResponseBodyFetchLink()
michael@0: {
michael@0: let content = this.httpActivity.response.content;
michael@0:
michael@0: let elem = this._appendTextNode("responseBodyFetchLink",
michael@0: this._format("fetchRemainingResponseContentLink",
michael@0: [content.text.length - content.text.initial.length]));
michael@0:
michael@0: elem.style.display = "block";
michael@0: elem.addEventListener("mousedown", this._responseBodyFetch);
michael@0: },
michael@0:
michael@0: /**
michael@0: * Click event handler for the link that allows users to fetch the remaining
michael@0: * response body.
michael@0: *
michael@0: * @private
michael@0: * @param nsIDOMEvent aEvent
michael@0: */
michael@0: _responseBodyFetch: function NP__responseBodyFetch(aEvent)
michael@0: {
michael@0: aEvent.target.style.display = "none";
michael@0: aEvent.target.removeEventListener("mousedown", this._responseBodyFetch);
michael@0:
michael@0: let content = this.httpActivity.response.content;
michael@0: let longString = this.webconsole.webConsoleClient.longString(content.text);
michael@0: longString.substring(longString.initial.length, longString.length,
michael@0: function NP__onLongStringSubstring(aResponse)
michael@0: {
michael@0: if (aResponse.error) {
michael@0: Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
michael@0: return;
michael@0: }
michael@0:
michael@0: content.text = content.text.initial + aResponse.substring;
michael@0: let cached = this._isResponseCached ? "Cached" : "";
michael@0:
michael@0: if (this._responseIsImage) {
michael@0: let imageNode = this.document.getElementById("responseImage" +
michael@0: cached + "Node");
michael@0: imageNode.src =
michael@0: "data:" + this.contentType + ";base64," + content.text;
michael@0: }
michael@0: else {
michael@0: this._appendTextNode("responseBody" + cached + "Content",
michael@0: aResponse.substring);
michael@0: }
michael@0: }.bind(this));
michael@0: },
michael@0:
michael@0: /**
michael@0: * Displays the `Unknown Content-Type hint` and sets the duration between the
michael@0: * receiving of the response header on the NetworkPanel.
michael@0: *
michael@0: * @returns void
michael@0: */
michael@0: _displayResponseBodyUnknownType: function NP__displayResponseBodyUnknownType()
michael@0: {
michael@0: let timing = this.httpActivity.timings;
michael@0:
michael@0: this._displayNode("responseBodyUnknownType");
michael@0: this._appendTextNode("responseBodyUnknownTypeInfo",
michael@0: this._format("durationMS", [timing.receive]));
michael@0:
michael@0: this._appendTextNode("responseBodyUnknownTypeContent",
michael@0: this._format("responseBodyUnableToDisplay.content", [this.contentType]));
michael@0: },
michael@0:
michael@0: /**
michael@0: * Displays the `no response body` section and sets the the duration between
michael@0: * the receiving of the response header and the end of the request.
michael@0: *
michael@0: * @returns void
michael@0: */
michael@0: _displayNoResponseBody: function NP_displayNoResponseBody()
michael@0: {
michael@0: let timing = this.httpActivity.timings;
michael@0:
michael@0: this._displayNode("responseNoBody");
michael@0: this._appendTextNode("responseNoBodyInfo",
michael@0: this._format("durationMS", [timing.receive]));
michael@0: },
michael@0:
michael@0: /**
michael@0: * Updates the content of the NetworkPanel's iframe.
michael@0: *
michael@0: * @returns void
michael@0: */
michael@0: update: function NP_update()
michael@0: {
michael@0: if (!this.document || this.document.readyState != "complete") {
michael@0: return;
michael@0: }
michael@0:
michael@0: let updates = this.httpActivity.updates;
michael@0: let timing = this.httpActivity.timings;
michael@0: let request = this.httpActivity.request;
michael@0: let response = this.httpActivity.response;
michael@0:
michael@0: switch (this._state) {
michael@0: case this._INIT:
michael@0: this._displayRequestHeader();
michael@0: this._state = this._DISPLAYED_REQUEST_HEADER;
michael@0: // FALL THROUGH
michael@0:
michael@0: case this._DISPLAYED_REQUEST_HEADER:
michael@0: // Process the request body if there is one.
michael@0: if (!this.httpActivity.discardRequestBody && request.postData.text) {
michael@0: this._updateRequestBody();
michael@0: this._state = this._DISPLAYED_REQUEST_BODY;
michael@0: }
michael@0: // FALL THROUGH
michael@0:
michael@0: case this._DISPLAYED_REQUEST_BODY:
michael@0: if (!response.headers.length || !Object.keys(timing).length) {
michael@0: break;
michael@0: }
michael@0: this._displayResponseHeader();
michael@0: this._state = this._DISPLAYED_RESPONSE_HEADER;
michael@0: // FALL THROUGH
michael@0:
michael@0: case this._DISPLAYED_RESPONSE_HEADER:
michael@0: if (updates.indexOf("responseContent") == -1 ||
michael@0: updates.indexOf("eventTimings") == -1) {
michael@0: break;
michael@0: }
michael@0:
michael@0: this._state = this._TRANSITION_CLOSED;
michael@0: if (this.httpActivity.discardResponseBody) {
michael@0: break;
michael@0: }
michael@0:
michael@0: if (!response.content || !response.content.text) {
michael@0: this._displayNoResponseBody();
michael@0: }
michael@0: else if (this._responseIsImage) {
michael@0: this._displayResponseImage();
michael@0: }
michael@0: else if (!this._isResponseBodyTextData) {
michael@0: this._displayResponseBodyUnknownType();
michael@0: }
michael@0: else if (response.content.text) {
michael@0: this._displayResponseBody();
michael@0: }
michael@0: break;
michael@0: }
michael@0:
michael@0: if (this._onUpdate) {
michael@0: this._onUpdate();
michael@0: }
michael@0: },
michael@0:
michael@0: /**
michael@0: * Update the panel to hold the current information we have about the request
michael@0: * body.
michael@0: * @private
michael@0: */
michael@0: _updateRequestBody: function NP__updateRequestBody()
michael@0: {
michael@0: let postData = this.httpActivity.request.postData;
michael@0: if (typeof postData.text == "object" && postData.text.type == "longString") {
michael@0: let elem = this._appendTextNode("requestBodyFetchLink",
michael@0: this._format("fetchRemainingRequestContentLink",
michael@0: [postData.text.length - postData.text.initial.length]));
michael@0:
michael@0: elem.style.display = "block";
michael@0: elem.addEventListener("mousedown", this._requestBodyFetch);
michael@0: return;
michael@0: }
michael@0:
michael@0: // Check if we send some form data. If so, display the form data special.
michael@0: if (this._isRequestBodyFormData) {
michael@0: this._displayRequestForm();
michael@0: }
michael@0: else {
michael@0: this._displayRequestBody();
michael@0: }
michael@0: },
michael@0:
michael@0: /**
michael@0: * Click event handler for the link that allows users to fetch the remaining
michael@0: * request body.
michael@0: *
michael@0: * @private
michael@0: * @param nsIDOMEvent aEvent
michael@0: */
michael@0: _requestBodyFetch: function NP__requestBodyFetch(aEvent)
michael@0: {
michael@0: aEvent.target.style.display = "none";
michael@0: aEvent.target.removeEventListener("mousedown", this._responseBodyFetch);
michael@0:
michael@0: let postData = this.httpActivity.request.postData;
michael@0: let longString = this.webconsole.webConsoleClient.longString(postData.text);
michael@0: longString.substring(longString.initial.length, longString.length,
michael@0: function NP__onLongStringSubstring(aResponse)
michael@0: {
michael@0: if (aResponse.error) {
michael@0: Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
michael@0: return;
michael@0: }
michael@0:
michael@0: postData.text = postData.text.initial + aResponse.substring;
michael@0: this._updateRequestBody();
michael@0: }.bind(this));
michael@0: },
michael@0: };
michael@0:
michael@0: /**
michael@0: * Creates a DOMNode and sets all the attributes of aAttributes on the created
michael@0: * element.
michael@0: *
michael@0: * @param nsIDOMDocument aDocument
michael@0: * Document to create the new DOMNode.
michael@0: * @param string aTag
michael@0: * Name of the tag for the DOMNode.
michael@0: * @param object aAttributes
michael@0: * Attributes set on the created DOMNode.
michael@0: *
michael@0: * @returns nsIDOMNode
michael@0: */
michael@0: function createElement(aDocument, aTag, aAttributes)
michael@0: {
michael@0: let node = aDocument.createElement(aTag);
michael@0: if (aAttributes) {
michael@0: for (let attr in aAttributes) {
michael@0: node.setAttribute(attr, aAttributes[attr]);
michael@0: }
michael@0: }
michael@0: return node;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Creates a new DOMNode and appends it to aParent.
michael@0: *
michael@0: * @param nsIDOMNode aParent
michael@0: * A parent node to append the created element.
michael@0: * @param string aTag
michael@0: * Name of the tag for the DOMNode.
michael@0: * @param object aAttributes
michael@0: * Attributes set on the created DOMNode.
michael@0: *
michael@0: * @returns nsIDOMNode
michael@0: */
michael@0: function createAndAppendElement(aParent, aTag, aAttributes)
michael@0: {
michael@0: let node = createElement(aParent.ownerDocument, aTag, aAttributes);
michael@0: aParent.appendChild(node);
michael@0: return node;
michael@0: }