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 +}