browser/devtools/webconsole/network-panel.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/devtools/webconsole/network-panel.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,836 @@
     1.4 +/* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
     1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +"use strict";
    1.11 +
    1.12 +const {Cc, Ci, Cu} = require("chrome");
    1.13 +
    1.14 +loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
    1.15 +loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
    1.16 +loader.lazyServiceGetter(this, "mimeService", "@mozilla.org/mime;1", "nsIMIMEService");
    1.17 +
    1.18 +let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
    1.19 +
    1.20 +const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
    1.21 +let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
    1.22 +
    1.23 +
    1.24 +/**
    1.25 + * Creates a new NetworkPanel.
    1.26 + *
    1.27 + * @constructor
    1.28 + * @param nsIDOMNode aParent
    1.29 + *        Parent node to append the created panel to.
    1.30 + * @param object aHttpActivity
    1.31 + *        HttpActivity to display in the panel.
    1.32 + * @param object aWebConsoleFrame
    1.33 + *        The parent WebConsoleFrame object that owns this network panel
    1.34 + *        instance.
    1.35 + */
    1.36 +function NetworkPanel(aParent, aHttpActivity, aWebConsoleFrame)
    1.37 +{
    1.38 +  let doc = aParent.ownerDocument;
    1.39 +  this.httpActivity = aHttpActivity;
    1.40 +  this.webconsole = aWebConsoleFrame;
    1.41 +  this._longStringClick = this._longStringClick.bind(this);
    1.42 +  this._responseBodyFetch = this._responseBodyFetch.bind(this);
    1.43 +  this._requestBodyFetch = this._requestBodyFetch.bind(this);
    1.44 +
    1.45 +  // Create the underlaying panel
    1.46 +  this.panel = createElement(doc, "panel", {
    1.47 +    label: l10n.getStr("NetworkPanel.label"),
    1.48 +    titlebar: "normal",
    1.49 +    noautofocus: "true",
    1.50 +    noautohide: "true",
    1.51 +    close: "true"
    1.52 +  });
    1.53 +
    1.54 +  // Create the iframe that displays the NetworkPanel XHTML.
    1.55 +  this.iframe = createAndAppendElement(this.panel, "iframe", {
    1.56 +    src: "chrome://browser/content/devtools/NetworkPanel.xhtml",
    1.57 +    type: "content",
    1.58 +    flex: "1"
    1.59 +  });
    1.60 +
    1.61 +  let self = this;
    1.62 +
    1.63 +  // Destroy the panel when it's closed.
    1.64 +  this.panel.addEventListener("popuphidden", function onPopupHide() {
    1.65 +    self.panel.removeEventListener("popuphidden", onPopupHide, false);
    1.66 +    self.panel.parentNode.removeChild(self.panel);
    1.67 +    self.panel = null;
    1.68 +    self.iframe = null;
    1.69 +    self.httpActivity = null;
    1.70 +    self.webconsole = null;
    1.71 +
    1.72 +    if (self.linkNode) {
    1.73 +      self.linkNode._panelOpen = false;
    1.74 +      self.linkNode = null;
    1.75 +    }
    1.76 +  }, false);
    1.77 +
    1.78 +  // Set the document object and update the content once the panel is loaded.
    1.79 +  this.iframe.addEventListener("load", function onLoad() {
    1.80 +    if (!self.iframe) {
    1.81 +      return;
    1.82 +    }
    1.83 +
    1.84 +    self.iframe.removeEventListener("load", onLoad, true);
    1.85 +    self.update();
    1.86 +  }, true);
    1.87 +
    1.88 +  this.panel.addEventListener("popupshown", function onPopupShown() {
    1.89 +    self.panel.removeEventListener("popupshown", onPopupShown, true);
    1.90 +    self.update();
    1.91 +  }, true);
    1.92 +
    1.93 +  // Create the footer.
    1.94 +  let footer = createElement(doc, "hbox", { align: "end" });
    1.95 +  createAndAppendElement(footer, "spacer", { flex: 1 });
    1.96 +
    1.97 +  createAndAppendElement(footer, "resizer", { dir: "bottomend" });
    1.98 +  this.panel.appendChild(footer);
    1.99 +
   1.100 +  aParent.appendChild(this.panel);
   1.101 +}
   1.102 +exports.NetworkPanel = NetworkPanel;
   1.103 +
   1.104 +NetworkPanel.prototype =
   1.105 +{
   1.106 +  /**
   1.107 +   * The current state of the output.
   1.108 +   */
   1.109 +  _state: 0,
   1.110 +
   1.111 +  /**
   1.112 +   * State variables.
   1.113 +   */
   1.114 +  _INIT: 0,
   1.115 +  _DISPLAYED_REQUEST_HEADER: 1,
   1.116 +  _DISPLAYED_REQUEST_BODY: 2,
   1.117 +  _DISPLAYED_RESPONSE_HEADER: 3,
   1.118 +  _TRANSITION_CLOSED: 4,
   1.119 +
   1.120 +  _fromDataRegExp: /Content-Type\:\s*application\/x-www-form-urlencoded/,
   1.121 +
   1.122 +  _contentType: null,
   1.123 +
   1.124 +  /**
   1.125 +   * Function callback invoked whenever the panel content is updated. This is
   1.126 +   * used only by tests.
   1.127 +   *
   1.128 +   * @private
   1.129 +   * @type function
   1.130 +   */
   1.131 +  _onUpdate: null,
   1.132 +
   1.133 +  get document() {
   1.134 +    return this.iframe && this.iframe.contentWindow ?
   1.135 +           this.iframe.contentWindow.document : null;
   1.136 +  },
   1.137 +
   1.138 +  /**
   1.139 +   * Small helper function that is nearly equal to l10n.getFormatStr
   1.140 +   * except that it prefixes aName with "NetworkPanel.".
   1.141 +   *
   1.142 +   * @param string aName
   1.143 +   *        The name of an i10n string to format. This string is prefixed with
   1.144 +   *        "NetworkPanel." before calling the HUDService.getFormatStr function.
   1.145 +   * @param array aArray
   1.146 +   *        Values used as placeholder for the i10n string.
   1.147 +   * @returns string
   1.148 +   *          The i10n formated string.
   1.149 +   */
   1.150 +  _format: function NP_format(aName, aArray)
   1.151 +  {
   1.152 +    return l10n.getFormatStr("NetworkPanel." + aName, aArray);
   1.153 +  },
   1.154 +
   1.155 +  /**
   1.156 +   * Returns the content type of the response body. This is based on the
   1.157 +   * response.content.mimeType property. If this value is not available, then
   1.158 +   * the content type is guessed by the file extension of the request URL.
   1.159 +   *
   1.160 +   * @return string
   1.161 +   *         Content type or empty string if no content type could be figured
   1.162 +   *         out.
   1.163 +   */
   1.164 +  get contentType()
   1.165 +  {
   1.166 +    if (this._contentType) {
   1.167 +      return this._contentType;
   1.168 +    }
   1.169 +
   1.170 +    let request = this.httpActivity.request;
   1.171 +    let response = this.httpActivity.response;
   1.172 +
   1.173 +    let contentType = "";
   1.174 +    let types = response.content ?
   1.175 +                (response.content.mimeType || "").split(/,|;/) : [];
   1.176 +    for (let i = 0; i < types.length; i++) {
   1.177 +      if (types[i] in NetworkHelper.mimeCategoryMap) {
   1.178 +        contentType = types[i];
   1.179 +        break;
   1.180 +      }
   1.181 +    }
   1.182 +
   1.183 +    if (contentType) {
   1.184 +      this._contentType = contentType;
   1.185 +      return contentType;
   1.186 +    }
   1.187 +
   1.188 +    // Try to get the content type from the request file extension.
   1.189 +    let uri = NetUtil.newURI(request.url);
   1.190 +    if ((uri instanceof Ci.nsIURL) && uri.fileExtension) {
   1.191 +      try {
   1.192 +         contentType = mimeService.getTypeFromExtension(uri.fileExtension);
   1.193 +      }
   1.194 +      catch(ex) {
   1.195 +        // Added to prevent failures on OS X 64. No Flash?
   1.196 +        Cu.reportError(ex);
   1.197 +      }
   1.198 +    }
   1.199 +
   1.200 +    this._contentType = contentType;
   1.201 +    return contentType;
   1.202 +  },
   1.203 +
   1.204 +  /**
   1.205 +   *
   1.206 +   * @returns boolean
   1.207 +   *          True if the response is an image, false otherwise.
   1.208 +   */
   1.209 +  get _responseIsImage()
   1.210 +  {
   1.211 +    return this.contentType &&
   1.212 +           NetworkHelper.mimeCategoryMap[this.contentType] == "image";
   1.213 +  },
   1.214 +
   1.215 +  /**
   1.216 +   *
   1.217 +   * @returns boolean
   1.218 +   *          True if the response body contains text, false otherwise.
   1.219 +   */
   1.220 +  get _isResponseBodyTextData()
   1.221 +  {
   1.222 +    return this.contentType ?
   1.223 +           NetworkHelper.isTextMimeType(this.contentType) : false;
   1.224 +  },
   1.225 +
   1.226 +  /**
   1.227 +   * Tells if the server response is cached.
   1.228 +   *
   1.229 +   * @returns boolean
   1.230 +   *          Returns true if the server responded that the request is already
   1.231 +   *          in the browser's cache, false otherwise.
   1.232 +   */
   1.233 +  get _isResponseCached()
   1.234 +  {
   1.235 +    return this.httpActivity.response.status == 304;
   1.236 +  },
   1.237 +
   1.238 +  /**
   1.239 +   * Tells if the request body includes form data.
   1.240 +   *
   1.241 +   * @returns boolean
   1.242 +   *          Returns true if the posted body contains form data.
   1.243 +   */
   1.244 +  get _isRequestBodyFormData()
   1.245 +  {
   1.246 +    let requestBody = this.httpActivity.request.postData.text;
   1.247 +    if (typeof requestBody == "object" && requestBody.type == "longString") {
   1.248 +      requestBody = requestBody.initial;
   1.249 +    }
   1.250 +    return this._fromDataRegExp.test(requestBody);
   1.251 +  },
   1.252 +
   1.253 +  /**
   1.254 +   * Appends the node with id=aId by the text aValue.
   1.255 +   *
   1.256 +   * @private
   1.257 +   * @param string aId
   1.258 +   * @param string aValue
   1.259 +   * @return nsIDOMElement
   1.260 +   *         The DOM element with id=aId.
   1.261 +   */
   1.262 +  _appendTextNode: function NP__appendTextNode(aId, aValue)
   1.263 +  {
   1.264 +    let textNode = this.document.createTextNode(aValue);
   1.265 +    let elem = this.document.getElementById(aId);
   1.266 +    elem.appendChild(textNode);
   1.267 +    return elem;
   1.268 +  },
   1.269 +
   1.270 +  /**
   1.271 +   * Generates some HTML to display the key-value pair of the aList data. The
   1.272 +   * generated HTML is added to node with id=aParentId.
   1.273 +   *
   1.274 +   * @param string aParentId
   1.275 +   *        Id of the parent node to append the list to.
   1.276 +   * @oaram array aList
   1.277 +   *        Array that holds the objects you want to display. Each object must
   1.278 +   *        have two properties: name and value.
   1.279 +   * @param boolean aIgnoreCookie
   1.280 +   *        If true, the key-value named "Cookie" is not added to the list.
   1.281 +   * @returns void
   1.282 +   */
   1.283 +  _appendList: function NP_appendList(aParentId, aList, aIgnoreCookie)
   1.284 +  {
   1.285 +    let parent = this.document.getElementById(aParentId);
   1.286 +    let doc = this.document;
   1.287 +
   1.288 +    aList.sort(function(a, b) {
   1.289 +      return a.name.toLowerCase() < b.name.toLowerCase();
   1.290 +    });
   1.291 +
   1.292 +    aList.forEach(function(aItem) {
   1.293 +      let name = aItem.name;
   1.294 +      if (aIgnoreCookie && (name == "Cookie" || name == "Set-Cookie")) {
   1.295 +        return;
   1.296 +      }
   1.297 +
   1.298 +      let value = aItem.value;
   1.299 +      let longString = null;
   1.300 +      if (typeof value == "object" && value.type == "longString") {
   1.301 +        value = value.initial;
   1.302 +        longString = true;
   1.303 +      }
   1.304 +
   1.305 +      /**
   1.306 +       * The following code creates the HTML:
   1.307 +       * <tr>
   1.308 +       * <th scope="row" class="property-name">${line}:</th>
   1.309 +       * <td class="property-value">${aList[line]}</td>
   1.310 +       * </tr>
   1.311 +       * and adds it to parent.
   1.312 +       */
   1.313 +      let row = doc.createElement("tr");
   1.314 +      let textNode = doc.createTextNode(name + ":");
   1.315 +      let th = doc.createElement("th");
   1.316 +      th.setAttribute("scope", "row");
   1.317 +      th.setAttribute("class", "property-name");
   1.318 +      th.appendChild(textNode);
   1.319 +      row.appendChild(th);
   1.320 +
   1.321 +      textNode = doc.createTextNode(value);
   1.322 +      let td = doc.createElement("td");
   1.323 +      td.setAttribute("class", "property-value");
   1.324 +      td.appendChild(textNode);
   1.325 +
   1.326 +      if (longString) {
   1.327 +        let a = doc.createElement("a");
   1.328 +        a.href = "#";
   1.329 +        a.className = "longStringEllipsis";
   1.330 +        a.addEventListener("mousedown", this._longStringClick.bind(this, aItem));
   1.331 +        a.textContent = l10n.getStr("longStringEllipsis");
   1.332 +        td.appendChild(a);
   1.333 +      }
   1.334 +
   1.335 +      row.appendChild(td);
   1.336 +
   1.337 +      parent.appendChild(row);
   1.338 +    }.bind(this));
   1.339 +  },
   1.340 +
   1.341 +  /**
   1.342 +   * The click event handler for the ellipsis which allows the user to retrieve
   1.343 +   * the full header value.
   1.344 +   *
   1.345 +   * @private
   1.346 +   * @param object aHeader
   1.347 +   *        The header object with the |name| and |value| properties.
   1.348 +   * @param nsIDOMEvent aEvent
   1.349 +   *        The DOM click event object.
   1.350 +   */
   1.351 +  _longStringClick: function NP__longStringClick(aHeader, aEvent)
   1.352 +  {
   1.353 +    aEvent.preventDefault();
   1.354 +
   1.355 +    let longString = this.webconsole.webConsoleClient.longString(aHeader.value);
   1.356 +
   1.357 +    longString.substring(longString.initial.length, longString.length,
   1.358 +      function NP__onLongStringSubstring(aResponse)
   1.359 +      {
   1.360 +        if (aResponse.error) {
   1.361 +          Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
   1.362 +          return;
   1.363 +        }
   1.364 +
   1.365 +        aHeader.value = aHeader.value.initial + aResponse.substring;
   1.366 +
   1.367 +        let textNode = aEvent.target.previousSibling;
   1.368 +        textNode.textContent += aResponse.substring;
   1.369 +        textNode.parentNode.removeChild(aEvent.target);
   1.370 +      });
   1.371 +  },
   1.372 +
   1.373 +  /**
   1.374 +   * Displays the node with id=aId.
   1.375 +   *
   1.376 +   * @private
   1.377 +   * @param string aId
   1.378 +   * @return nsIDOMElement
   1.379 +   *         The element with id=aId.
   1.380 +   */
   1.381 +  _displayNode: function NP__displayNode(aId)
   1.382 +  {
   1.383 +    let elem = this.document.getElementById(aId);
   1.384 +    elem.style.display = "block";
   1.385 +  },
   1.386 +
   1.387 +  /**
   1.388 +   * Sets the request URL, request method, the timing information when the
   1.389 +   * request started and the request header content on the NetworkPanel.
   1.390 +   * If the request header contains cookie data, a list of sent cookies is
   1.391 +   * generated and a special sent cookie section is displayed + the cookie list
   1.392 +   * added to it.
   1.393 +   *
   1.394 +   * @returns void
   1.395 +   */
   1.396 +  _displayRequestHeader: function NP__displayRequestHeader()
   1.397 +  {
   1.398 +    let request = this.httpActivity.request;
   1.399 +    let requestTime = new Date(this.httpActivity.startedDateTime);
   1.400 +
   1.401 +    this._appendTextNode("headUrl", request.url);
   1.402 +    this._appendTextNode("headMethod", request.method);
   1.403 +    this._appendTextNode("requestHeadersInfo",
   1.404 +                         l10n.timestampString(requestTime));
   1.405 +
   1.406 +    this._appendList("requestHeadersContent", request.headers, true);
   1.407 +
   1.408 +    if (request.cookies.length > 0) {
   1.409 +      this._displayNode("requestCookie");
   1.410 +      this._appendList("requestCookieContent", request.cookies);
   1.411 +    }
   1.412 +  },
   1.413 +
   1.414 +  /**
   1.415 +   * Displays the request body section of the NetworkPanel and set the request
   1.416 +   * body content on the NetworkPanel.
   1.417 +   *
   1.418 +   * @returns void
   1.419 +   */
   1.420 +  _displayRequestBody: function NP__displayRequestBody()
   1.421 +  {
   1.422 +    let postData = this.httpActivity.request.postData;
   1.423 +    this._displayNode("requestBody");
   1.424 +    this._appendTextNode("requestBodyContent", postData.text);
   1.425 +  },
   1.426 +
   1.427 +  /*
   1.428 +   * Displays the `sent form data` section. Parses the request header for the
   1.429 +   * submitted form data displays it inside of the `sent form data` section.
   1.430 +   *
   1.431 +   * @returns void
   1.432 +   */
   1.433 +  _displayRequestForm: function NP__processRequestForm()
   1.434 +  {
   1.435 +    let postData = this.httpActivity.request.postData.text;
   1.436 +    let requestBodyLines = postData.split("\n");
   1.437 +    let formData = requestBodyLines[requestBodyLines.length - 1].
   1.438 +                      replace(/\+/g, " ").split("&");
   1.439 +
   1.440 +    function unescapeText(aText)
   1.441 +    {
   1.442 +      try {
   1.443 +        return decodeURIComponent(aText);
   1.444 +      }
   1.445 +      catch (ex) {
   1.446 +        return decodeURIComponent(unescape(aText));
   1.447 +      }
   1.448 +    }
   1.449 +
   1.450 +    let formDataArray = [];
   1.451 +    for (let i = 0; i < formData.length; i++) {
   1.452 +      let data = formData[i];
   1.453 +      let idx = data.indexOf("=");
   1.454 +      let key = data.substring(0, idx);
   1.455 +      let value = data.substring(idx + 1);
   1.456 +      formDataArray.push({
   1.457 +        name: unescapeText(key),
   1.458 +        value: unescapeText(value)
   1.459 +      });
   1.460 +    }
   1.461 +
   1.462 +    this._appendList("requestFormDataContent", formDataArray);
   1.463 +    this._displayNode("requestFormData");
   1.464 +  },
   1.465 +
   1.466 +  /**
   1.467 +   * Displays the response section of the NetworkPanel, sets the response status,
   1.468 +   * the duration between the start of the request and the receiving of the
   1.469 +   * response header as well as the response header content on the the NetworkPanel.
   1.470 +   *
   1.471 +   * @returns void
   1.472 +   */
   1.473 +  _displayResponseHeader: function NP__displayResponseHeader()
   1.474 +  {
   1.475 +    let timing = this.httpActivity.timings;
   1.476 +    let response = this.httpActivity.response;
   1.477 +
   1.478 +    this._appendTextNode("headStatus",
   1.479 +                         [response.httpVersion, response.status,
   1.480 +                          response.statusText].join(" "));
   1.481 +
   1.482 +    // Calculate how much time it took from the request start, until the
   1.483 +    // response started to be received.
   1.484 +    let deltaDuration = 0;
   1.485 +    ["dns", "connect", "send", "wait"].forEach(function (aValue) {
   1.486 +      let ms = timing[aValue];
   1.487 +      if (ms > -1) {
   1.488 +        deltaDuration += ms;
   1.489 +      }
   1.490 +    });
   1.491 +
   1.492 +    this._appendTextNode("responseHeadersInfo",
   1.493 +      this._format("durationMS", [deltaDuration]));
   1.494 +
   1.495 +    this._displayNode("responseContainer");
   1.496 +    this._appendList("responseHeadersContent", response.headers, true);
   1.497 +
   1.498 +    if (response.cookies.length > 0) {
   1.499 +      this._displayNode("responseCookie");
   1.500 +      this._appendList("responseCookieContent", response.cookies);
   1.501 +    }
   1.502 +  },
   1.503 +
   1.504 +  /**
   1.505 +   * Displays the respones image section, sets the source of the image displayed
   1.506 +   * in the image response section to the request URL and the duration between
   1.507 +   * the receiving of the response header and the end of the request. Once the
   1.508 +   * image is loaded, the size of the requested image is set.
   1.509 +   *
   1.510 +   * @returns void
   1.511 +   */
   1.512 +  _displayResponseImage: function NP__displayResponseImage()
   1.513 +  {
   1.514 +    let self = this;
   1.515 +    let timing = this.httpActivity.timings;
   1.516 +    let request = this.httpActivity.request;
   1.517 +    let response = this.httpActivity.response;
   1.518 +    let cached = "";
   1.519 +
   1.520 +    if (this._isResponseCached) {
   1.521 +      cached = "Cached";
   1.522 +    }
   1.523 +
   1.524 +    let imageNode = this.document.getElementById("responseImage" +
   1.525 +                                                 cached + "Node");
   1.526 +
   1.527 +    let text = response.content.text;
   1.528 +    if (typeof text == "object" && text.type == "longString") {
   1.529 +      this._showResponseBodyFetchLink();
   1.530 +    }
   1.531 +    else {
   1.532 +      imageNode.setAttribute("src",
   1.533 +        "data:" + this.contentType + ";base64," + text);
   1.534 +    }
   1.535 +
   1.536 +    // This function is called to set the imageInfo.
   1.537 +    function setImageInfo() {
   1.538 +      self._appendTextNode("responseImage" + cached + "Info",
   1.539 +        self._format("imageSizeDeltaDurationMS",
   1.540 +          [ imageNode.width, imageNode.height, timing.receive ]
   1.541 +        )
   1.542 +      );
   1.543 +    }
   1.544 +
   1.545 +    // Check if the image is already loaded.
   1.546 +    if (imageNode.width != 0) {
   1.547 +      setImageInfo();
   1.548 +    }
   1.549 +    else {
   1.550 +      // Image is not loaded yet therefore add a load event.
   1.551 +      imageNode.addEventListener("load", function imageNodeLoad() {
   1.552 +        imageNode.removeEventListener("load", imageNodeLoad, false);
   1.553 +        setImageInfo();
   1.554 +      }, false);
   1.555 +    }
   1.556 +
   1.557 +    this._displayNode("responseImage" + cached);
   1.558 +  },
   1.559 +
   1.560 +  /**
   1.561 +   * Displays the response body section, sets the the duration between
   1.562 +   * the receiving of the response header and the end of the request as well as
   1.563 +   * the content of the response body on the NetworkPanel.
   1.564 +   *
   1.565 +   * @returns void
   1.566 +   */
   1.567 +  _displayResponseBody: function NP__displayResponseBody()
   1.568 +  {
   1.569 +    let timing = this.httpActivity.timings;
   1.570 +    let response = this.httpActivity.response;
   1.571 +    let cached =  this._isResponseCached ? "Cached" : "";
   1.572 +
   1.573 +    this._appendTextNode("responseBody" + cached + "Info",
   1.574 +      this._format("durationMS", [timing.receive]));
   1.575 +
   1.576 +    this._displayNode("responseBody" + cached);
   1.577 +
   1.578 +    let text = response.content.text;
   1.579 +    if (typeof text == "object") {
   1.580 +      text = text.initial;
   1.581 +      this._showResponseBodyFetchLink();
   1.582 +    }
   1.583 +
   1.584 +    this._appendTextNode("responseBody" + cached + "Content", text);
   1.585 +  },
   1.586 +
   1.587 +  /**
   1.588 +   * Show the "fetch response body" link.
   1.589 +   * @private
   1.590 +   */
   1.591 +  _showResponseBodyFetchLink: function NP__showResponseBodyFetchLink()
   1.592 +  {
   1.593 +    let content = this.httpActivity.response.content;
   1.594 +
   1.595 +    let elem = this._appendTextNode("responseBodyFetchLink",
   1.596 +      this._format("fetchRemainingResponseContentLink",
   1.597 +                   [content.text.length - content.text.initial.length]));
   1.598 +
   1.599 +    elem.style.display = "block";
   1.600 +    elem.addEventListener("mousedown", this._responseBodyFetch);
   1.601 +  },
   1.602 +
   1.603 +  /**
   1.604 +   * Click event handler for the link that allows users to fetch the remaining
   1.605 +   * response body.
   1.606 +   *
   1.607 +   * @private
   1.608 +   * @param nsIDOMEvent aEvent
   1.609 +   */
   1.610 +  _responseBodyFetch: function NP__responseBodyFetch(aEvent)
   1.611 +  {
   1.612 +    aEvent.target.style.display = "none";
   1.613 +    aEvent.target.removeEventListener("mousedown", this._responseBodyFetch);
   1.614 +
   1.615 +    let content = this.httpActivity.response.content;
   1.616 +    let longString = this.webconsole.webConsoleClient.longString(content.text);
   1.617 +    longString.substring(longString.initial.length, longString.length,
   1.618 +      function NP__onLongStringSubstring(aResponse)
   1.619 +      {
   1.620 +        if (aResponse.error) {
   1.621 +          Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
   1.622 +          return;
   1.623 +        }
   1.624 +
   1.625 +        content.text = content.text.initial + aResponse.substring;
   1.626 +        let cached =  this._isResponseCached ? "Cached" : "";
   1.627 +
   1.628 +        if (this._responseIsImage) {
   1.629 +          let imageNode = this.document.getElementById("responseImage" +
   1.630 +                                                       cached + "Node");
   1.631 +          imageNode.src =
   1.632 +            "data:" + this.contentType + ";base64," + content.text;
   1.633 +        }
   1.634 +        else {
   1.635 +          this._appendTextNode("responseBody" + cached + "Content",
   1.636 +                               aResponse.substring);
   1.637 +        }
   1.638 +      }.bind(this));
   1.639 +  },
   1.640 +
   1.641 +  /**
   1.642 +   * Displays the `Unknown Content-Type hint` and sets the duration between the
   1.643 +   * receiving of the response header on the NetworkPanel.
   1.644 +   *
   1.645 +   * @returns void
   1.646 +   */
   1.647 +  _displayResponseBodyUnknownType: function NP__displayResponseBodyUnknownType()
   1.648 +  {
   1.649 +    let timing = this.httpActivity.timings;
   1.650 +
   1.651 +    this._displayNode("responseBodyUnknownType");
   1.652 +    this._appendTextNode("responseBodyUnknownTypeInfo",
   1.653 +      this._format("durationMS", [timing.receive]));
   1.654 +
   1.655 +    this._appendTextNode("responseBodyUnknownTypeContent",
   1.656 +      this._format("responseBodyUnableToDisplay.content", [this.contentType]));
   1.657 +  },
   1.658 +
   1.659 +  /**
   1.660 +   * Displays the `no response body` section and sets the the duration between
   1.661 +   * the receiving of the response header and the end of the request.
   1.662 +   *
   1.663 +   * @returns void
   1.664 +   */
   1.665 +  _displayNoResponseBody: function NP_displayNoResponseBody()
   1.666 +  {
   1.667 +    let timing = this.httpActivity.timings;
   1.668 +
   1.669 +    this._displayNode("responseNoBody");
   1.670 +    this._appendTextNode("responseNoBodyInfo",
   1.671 +      this._format("durationMS", [timing.receive]));
   1.672 +  },
   1.673 +
   1.674 +  /**
   1.675 +   * Updates the content of the NetworkPanel's iframe.
   1.676 +   *
   1.677 +   * @returns void
   1.678 +   */
   1.679 +  update: function NP_update()
   1.680 +  {
   1.681 +    if (!this.document || this.document.readyState != "complete") {
   1.682 +      return;
   1.683 +    }
   1.684 +
   1.685 +    let updates = this.httpActivity.updates;
   1.686 +    let timing = this.httpActivity.timings;
   1.687 +    let request = this.httpActivity.request;
   1.688 +    let response = this.httpActivity.response;
   1.689 +
   1.690 +    switch (this._state) {
   1.691 +      case this._INIT:
   1.692 +        this._displayRequestHeader();
   1.693 +        this._state = this._DISPLAYED_REQUEST_HEADER;
   1.694 +        // FALL THROUGH
   1.695 +
   1.696 +      case this._DISPLAYED_REQUEST_HEADER:
   1.697 +        // Process the request body if there is one.
   1.698 +        if (!this.httpActivity.discardRequestBody && request.postData.text) {
   1.699 +          this._updateRequestBody();
   1.700 +          this._state = this._DISPLAYED_REQUEST_BODY;
   1.701 +        }
   1.702 +        // FALL THROUGH
   1.703 +
   1.704 +      case this._DISPLAYED_REQUEST_BODY:
   1.705 +        if (!response.headers.length || !Object.keys(timing).length) {
   1.706 +          break;
   1.707 +        }
   1.708 +        this._displayResponseHeader();
   1.709 +        this._state = this._DISPLAYED_RESPONSE_HEADER;
   1.710 +        // FALL THROUGH
   1.711 +
   1.712 +      case this._DISPLAYED_RESPONSE_HEADER:
   1.713 +        if (updates.indexOf("responseContent") == -1 ||
   1.714 +            updates.indexOf("eventTimings") == -1) {
   1.715 +          break;
   1.716 +        }
   1.717 +
   1.718 +        this._state = this._TRANSITION_CLOSED;
   1.719 +        if (this.httpActivity.discardResponseBody) {
   1.720 +          break;
   1.721 +        }
   1.722 +
   1.723 +        if (!response.content || !response.content.text) {
   1.724 +          this._displayNoResponseBody();
   1.725 +        }
   1.726 +        else if (this._responseIsImage) {
   1.727 +          this._displayResponseImage();
   1.728 +        }
   1.729 +        else if (!this._isResponseBodyTextData) {
   1.730 +          this._displayResponseBodyUnknownType();
   1.731 +        }
   1.732 +        else if (response.content.text) {
   1.733 +          this._displayResponseBody();
   1.734 +        }
   1.735 +        break;
   1.736 +    }
   1.737 +
   1.738 +    if (this._onUpdate) {
   1.739 +      this._onUpdate();
   1.740 +    }
   1.741 +  },
   1.742 +
   1.743 +  /**
   1.744 +   * Update the panel to hold the current information we have about the request
   1.745 +   * body.
   1.746 +   * @private
   1.747 +   */
   1.748 +  _updateRequestBody: function NP__updateRequestBody()
   1.749 +  {
   1.750 +    let postData = this.httpActivity.request.postData;
   1.751 +    if (typeof postData.text == "object" && postData.text.type == "longString") {
   1.752 +      let elem = this._appendTextNode("requestBodyFetchLink",
   1.753 +        this._format("fetchRemainingRequestContentLink",
   1.754 +                     [postData.text.length - postData.text.initial.length]));
   1.755 +
   1.756 +      elem.style.display = "block";
   1.757 +      elem.addEventListener("mousedown", this._requestBodyFetch);
   1.758 +      return;
   1.759 +    }
   1.760 +
   1.761 +    // Check if we send some form data. If so, display the form data special.
   1.762 +    if (this._isRequestBodyFormData) {
   1.763 +      this._displayRequestForm();
   1.764 +    }
   1.765 +    else {
   1.766 +      this._displayRequestBody();
   1.767 +    }
   1.768 +  },
   1.769 +
   1.770 +  /**
   1.771 +   * Click event handler for the link that allows users to fetch the remaining
   1.772 +   * request body.
   1.773 +   *
   1.774 +   * @private
   1.775 +   * @param nsIDOMEvent aEvent
   1.776 +   */
   1.777 +  _requestBodyFetch: function NP__requestBodyFetch(aEvent)
   1.778 +  {
   1.779 +    aEvent.target.style.display = "none";
   1.780 +    aEvent.target.removeEventListener("mousedown", this._responseBodyFetch);
   1.781 +
   1.782 +    let postData = this.httpActivity.request.postData;
   1.783 +    let longString = this.webconsole.webConsoleClient.longString(postData.text);
   1.784 +    longString.substring(longString.initial.length, longString.length,
   1.785 +      function NP__onLongStringSubstring(aResponse)
   1.786 +      {
   1.787 +        if (aResponse.error) {
   1.788 +          Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
   1.789 +          return;
   1.790 +        }
   1.791 +
   1.792 +        postData.text = postData.text.initial + aResponse.substring;
   1.793 +        this._updateRequestBody();
   1.794 +      }.bind(this));
   1.795 +  },
   1.796 +};
   1.797 +
   1.798 +/**
   1.799 + * Creates a DOMNode and sets all the attributes of aAttributes on the created
   1.800 + * element.
   1.801 + *
   1.802 + * @param nsIDOMDocument aDocument
   1.803 + *        Document to create the new DOMNode.
   1.804 + * @param string aTag
   1.805 + *        Name of the tag for the DOMNode.
   1.806 + * @param object aAttributes
   1.807 + *        Attributes set on the created DOMNode.
   1.808 + *
   1.809 + * @returns nsIDOMNode
   1.810 + */
   1.811 +function createElement(aDocument, aTag, aAttributes)
   1.812 +{
   1.813 +  let node = aDocument.createElement(aTag);
   1.814 +  if (aAttributes) {
   1.815 +    for (let attr in aAttributes) {
   1.816 +      node.setAttribute(attr, aAttributes[attr]);
   1.817 +    }
   1.818 +  }
   1.819 +  return node;
   1.820 +}
   1.821 +
   1.822 +/**
   1.823 + * Creates a new DOMNode and appends it to aParent.
   1.824 + *
   1.825 + * @param nsIDOMNode aParent
   1.826 + *        A parent node to append the created element.
   1.827 + * @param string aTag
   1.828 + *        Name of the tag for the DOMNode.
   1.829 + * @param object aAttributes
   1.830 + *        Attributes set on the created DOMNode.
   1.831 + *
   1.832 + * @returns nsIDOMNode
   1.833 + */
   1.834 +function createAndAppendElement(aParent, aTag, aAttributes)
   1.835 +{
   1.836 +  let node = createElement(aParent.ownerDocument, aTag, aAttributes);
   1.837 +  aParent.appendChild(node);
   1.838 +  return node;
   1.839 +}

mercurial