|
1 /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ |
|
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 "use strict"; |
|
8 |
|
9 const {Cc, Ci, Cu} = require("chrome"); |
|
10 |
|
11 loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper")); |
|
12 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); |
|
13 loader.lazyServiceGetter(this, "mimeService", "@mozilla.org/mime;1", "nsIMIMEService"); |
|
14 |
|
15 let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils; |
|
16 |
|
17 const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties"; |
|
18 let l10n = new WebConsoleUtils.l10n(STRINGS_URI); |
|
19 |
|
20 |
|
21 /** |
|
22 * Creates a new NetworkPanel. |
|
23 * |
|
24 * @constructor |
|
25 * @param nsIDOMNode aParent |
|
26 * Parent node to append the created panel to. |
|
27 * @param object aHttpActivity |
|
28 * HttpActivity to display in the panel. |
|
29 * @param object aWebConsoleFrame |
|
30 * The parent WebConsoleFrame object that owns this network panel |
|
31 * instance. |
|
32 */ |
|
33 function NetworkPanel(aParent, aHttpActivity, aWebConsoleFrame) |
|
34 { |
|
35 let doc = aParent.ownerDocument; |
|
36 this.httpActivity = aHttpActivity; |
|
37 this.webconsole = aWebConsoleFrame; |
|
38 this._longStringClick = this._longStringClick.bind(this); |
|
39 this._responseBodyFetch = this._responseBodyFetch.bind(this); |
|
40 this._requestBodyFetch = this._requestBodyFetch.bind(this); |
|
41 |
|
42 // Create the underlaying panel |
|
43 this.panel = createElement(doc, "panel", { |
|
44 label: l10n.getStr("NetworkPanel.label"), |
|
45 titlebar: "normal", |
|
46 noautofocus: "true", |
|
47 noautohide: "true", |
|
48 close: "true" |
|
49 }); |
|
50 |
|
51 // Create the iframe that displays the NetworkPanel XHTML. |
|
52 this.iframe = createAndAppendElement(this.panel, "iframe", { |
|
53 src: "chrome://browser/content/devtools/NetworkPanel.xhtml", |
|
54 type: "content", |
|
55 flex: "1" |
|
56 }); |
|
57 |
|
58 let self = this; |
|
59 |
|
60 // Destroy the panel when it's closed. |
|
61 this.panel.addEventListener("popuphidden", function onPopupHide() { |
|
62 self.panel.removeEventListener("popuphidden", onPopupHide, false); |
|
63 self.panel.parentNode.removeChild(self.panel); |
|
64 self.panel = null; |
|
65 self.iframe = null; |
|
66 self.httpActivity = null; |
|
67 self.webconsole = null; |
|
68 |
|
69 if (self.linkNode) { |
|
70 self.linkNode._panelOpen = false; |
|
71 self.linkNode = null; |
|
72 } |
|
73 }, false); |
|
74 |
|
75 // Set the document object and update the content once the panel is loaded. |
|
76 this.iframe.addEventListener("load", function onLoad() { |
|
77 if (!self.iframe) { |
|
78 return; |
|
79 } |
|
80 |
|
81 self.iframe.removeEventListener("load", onLoad, true); |
|
82 self.update(); |
|
83 }, true); |
|
84 |
|
85 this.panel.addEventListener("popupshown", function onPopupShown() { |
|
86 self.panel.removeEventListener("popupshown", onPopupShown, true); |
|
87 self.update(); |
|
88 }, true); |
|
89 |
|
90 // Create the footer. |
|
91 let footer = createElement(doc, "hbox", { align: "end" }); |
|
92 createAndAppendElement(footer, "spacer", { flex: 1 }); |
|
93 |
|
94 createAndAppendElement(footer, "resizer", { dir: "bottomend" }); |
|
95 this.panel.appendChild(footer); |
|
96 |
|
97 aParent.appendChild(this.panel); |
|
98 } |
|
99 exports.NetworkPanel = NetworkPanel; |
|
100 |
|
101 NetworkPanel.prototype = |
|
102 { |
|
103 /** |
|
104 * The current state of the output. |
|
105 */ |
|
106 _state: 0, |
|
107 |
|
108 /** |
|
109 * State variables. |
|
110 */ |
|
111 _INIT: 0, |
|
112 _DISPLAYED_REQUEST_HEADER: 1, |
|
113 _DISPLAYED_REQUEST_BODY: 2, |
|
114 _DISPLAYED_RESPONSE_HEADER: 3, |
|
115 _TRANSITION_CLOSED: 4, |
|
116 |
|
117 _fromDataRegExp: /Content-Type\:\s*application\/x-www-form-urlencoded/, |
|
118 |
|
119 _contentType: null, |
|
120 |
|
121 /** |
|
122 * Function callback invoked whenever the panel content is updated. This is |
|
123 * used only by tests. |
|
124 * |
|
125 * @private |
|
126 * @type function |
|
127 */ |
|
128 _onUpdate: null, |
|
129 |
|
130 get document() { |
|
131 return this.iframe && this.iframe.contentWindow ? |
|
132 this.iframe.contentWindow.document : null; |
|
133 }, |
|
134 |
|
135 /** |
|
136 * Small helper function that is nearly equal to l10n.getFormatStr |
|
137 * except that it prefixes aName with "NetworkPanel.". |
|
138 * |
|
139 * @param string aName |
|
140 * The name of an i10n string to format. This string is prefixed with |
|
141 * "NetworkPanel." before calling the HUDService.getFormatStr function. |
|
142 * @param array aArray |
|
143 * Values used as placeholder for the i10n string. |
|
144 * @returns string |
|
145 * The i10n formated string. |
|
146 */ |
|
147 _format: function NP_format(aName, aArray) |
|
148 { |
|
149 return l10n.getFormatStr("NetworkPanel." + aName, aArray); |
|
150 }, |
|
151 |
|
152 /** |
|
153 * Returns the content type of the response body. This is based on the |
|
154 * response.content.mimeType property. If this value is not available, then |
|
155 * the content type is guessed by the file extension of the request URL. |
|
156 * |
|
157 * @return string |
|
158 * Content type or empty string if no content type could be figured |
|
159 * out. |
|
160 */ |
|
161 get contentType() |
|
162 { |
|
163 if (this._contentType) { |
|
164 return this._contentType; |
|
165 } |
|
166 |
|
167 let request = this.httpActivity.request; |
|
168 let response = this.httpActivity.response; |
|
169 |
|
170 let contentType = ""; |
|
171 let types = response.content ? |
|
172 (response.content.mimeType || "").split(/,|;/) : []; |
|
173 for (let i = 0; i < types.length; i++) { |
|
174 if (types[i] in NetworkHelper.mimeCategoryMap) { |
|
175 contentType = types[i]; |
|
176 break; |
|
177 } |
|
178 } |
|
179 |
|
180 if (contentType) { |
|
181 this._contentType = contentType; |
|
182 return contentType; |
|
183 } |
|
184 |
|
185 // Try to get the content type from the request file extension. |
|
186 let uri = NetUtil.newURI(request.url); |
|
187 if ((uri instanceof Ci.nsIURL) && uri.fileExtension) { |
|
188 try { |
|
189 contentType = mimeService.getTypeFromExtension(uri.fileExtension); |
|
190 } |
|
191 catch(ex) { |
|
192 // Added to prevent failures on OS X 64. No Flash? |
|
193 Cu.reportError(ex); |
|
194 } |
|
195 } |
|
196 |
|
197 this._contentType = contentType; |
|
198 return contentType; |
|
199 }, |
|
200 |
|
201 /** |
|
202 * |
|
203 * @returns boolean |
|
204 * True if the response is an image, false otherwise. |
|
205 */ |
|
206 get _responseIsImage() |
|
207 { |
|
208 return this.contentType && |
|
209 NetworkHelper.mimeCategoryMap[this.contentType] == "image"; |
|
210 }, |
|
211 |
|
212 /** |
|
213 * |
|
214 * @returns boolean |
|
215 * True if the response body contains text, false otherwise. |
|
216 */ |
|
217 get _isResponseBodyTextData() |
|
218 { |
|
219 return this.contentType ? |
|
220 NetworkHelper.isTextMimeType(this.contentType) : false; |
|
221 }, |
|
222 |
|
223 /** |
|
224 * Tells if the server response is cached. |
|
225 * |
|
226 * @returns boolean |
|
227 * Returns true if the server responded that the request is already |
|
228 * in the browser's cache, false otherwise. |
|
229 */ |
|
230 get _isResponseCached() |
|
231 { |
|
232 return this.httpActivity.response.status == 304; |
|
233 }, |
|
234 |
|
235 /** |
|
236 * Tells if the request body includes form data. |
|
237 * |
|
238 * @returns boolean |
|
239 * Returns true if the posted body contains form data. |
|
240 */ |
|
241 get _isRequestBodyFormData() |
|
242 { |
|
243 let requestBody = this.httpActivity.request.postData.text; |
|
244 if (typeof requestBody == "object" && requestBody.type == "longString") { |
|
245 requestBody = requestBody.initial; |
|
246 } |
|
247 return this._fromDataRegExp.test(requestBody); |
|
248 }, |
|
249 |
|
250 /** |
|
251 * Appends the node with id=aId by the text aValue. |
|
252 * |
|
253 * @private |
|
254 * @param string aId |
|
255 * @param string aValue |
|
256 * @return nsIDOMElement |
|
257 * The DOM element with id=aId. |
|
258 */ |
|
259 _appendTextNode: function NP__appendTextNode(aId, aValue) |
|
260 { |
|
261 let textNode = this.document.createTextNode(aValue); |
|
262 let elem = this.document.getElementById(aId); |
|
263 elem.appendChild(textNode); |
|
264 return elem; |
|
265 }, |
|
266 |
|
267 /** |
|
268 * Generates some HTML to display the key-value pair of the aList data. The |
|
269 * generated HTML is added to node with id=aParentId. |
|
270 * |
|
271 * @param string aParentId |
|
272 * Id of the parent node to append the list to. |
|
273 * @oaram array aList |
|
274 * Array that holds the objects you want to display. Each object must |
|
275 * have two properties: name and value. |
|
276 * @param boolean aIgnoreCookie |
|
277 * If true, the key-value named "Cookie" is not added to the list. |
|
278 * @returns void |
|
279 */ |
|
280 _appendList: function NP_appendList(aParentId, aList, aIgnoreCookie) |
|
281 { |
|
282 let parent = this.document.getElementById(aParentId); |
|
283 let doc = this.document; |
|
284 |
|
285 aList.sort(function(a, b) { |
|
286 return a.name.toLowerCase() < b.name.toLowerCase(); |
|
287 }); |
|
288 |
|
289 aList.forEach(function(aItem) { |
|
290 let name = aItem.name; |
|
291 if (aIgnoreCookie && (name == "Cookie" || name == "Set-Cookie")) { |
|
292 return; |
|
293 } |
|
294 |
|
295 let value = aItem.value; |
|
296 let longString = null; |
|
297 if (typeof value == "object" && value.type == "longString") { |
|
298 value = value.initial; |
|
299 longString = true; |
|
300 } |
|
301 |
|
302 /** |
|
303 * The following code creates the HTML: |
|
304 * <tr> |
|
305 * <th scope="row" class="property-name">${line}:</th> |
|
306 * <td class="property-value">${aList[line]}</td> |
|
307 * </tr> |
|
308 * and adds it to parent. |
|
309 */ |
|
310 let row = doc.createElement("tr"); |
|
311 let textNode = doc.createTextNode(name + ":"); |
|
312 let th = doc.createElement("th"); |
|
313 th.setAttribute("scope", "row"); |
|
314 th.setAttribute("class", "property-name"); |
|
315 th.appendChild(textNode); |
|
316 row.appendChild(th); |
|
317 |
|
318 textNode = doc.createTextNode(value); |
|
319 let td = doc.createElement("td"); |
|
320 td.setAttribute("class", "property-value"); |
|
321 td.appendChild(textNode); |
|
322 |
|
323 if (longString) { |
|
324 let a = doc.createElement("a"); |
|
325 a.href = "#"; |
|
326 a.className = "longStringEllipsis"; |
|
327 a.addEventListener("mousedown", this._longStringClick.bind(this, aItem)); |
|
328 a.textContent = l10n.getStr("longStringEllipsis"); |
|
329 td.appendChild(a); |
|
330 } |
|
331 |
|
332 row.appendChild(td); |
|
333 |
|
334 parent.appendChild(row); |
|
335 }.bind(this)); |
|
336 }, |
|
337 |
|
338 /** |
|
339 * The click event handler for the ellipsis which allows the user to retrieve |
|
340 * the full header value. |
|
341 * |
|
342 * @private |
|
343 * @param object aHeader |
|
344 * The header object with the |name| and |value| properties. |
|
345 * @param nsIDOMEvent aEvent |
|
346 * The DOM click event object. |
|
347 */ |
|
348 _longStringClick: function NP__longStringClick(aHeader, aEvent) |
|
349 { |
|
350 aEvent.preventDefault(); |
|
351 |
|
352 let longString = this.webconsole.webConsoleClient.longString(aHeader.value); |
|
353 |
|
354 longString.substring(longString.initial.length, longString.length, |
|
355 function NP__onLongStringSubstring(aResponse) |
|
356 { |
|
357 if (aResponse.error) { |
|
358 Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error); |
|
359 return; |
|
360 } |
|
361 |
|
362 aHeader.value = aHeader.value.initial + aResponse.substring; |
|
363 |
|
364 let textNode = aEvent.target.previousSibling; |
|
365 textNode.textContent += aResponse.substring; |
|
366 textNode.parentNode.removeChild(aEvent.target); |
|
367 }); |
|
368 }, |
|
369 |
|
370 /** |
|
371 * Displays the node with id=aId. |
|
372 * |
|
373 * @private |
|
374 * @param string aId |
|
375 * @return nsIDOMElement |
|
376 * The element with id=aId. |
|
377 */ |
|
378 _displayNode: function NP__displayNode(aId) |
|
379 { |
|
380 let elem = this.document.getElementById(aId); |
|
381 elem.style.display = "block"; |
|
382 }, |
|
383 |
|
384 /** |
|
385 * Sets the request URL, request method, the timing information when the |
|
386 * request started and the request header content on the NetworkPanel. |
|
387 * If the request header contains cookie data, a list of sent cookies is |
|
388 * generated and a special sent cookie section is displayed + the cookie list |
|
389 * added to it. |
|
390 * |
|
391 * @returns void |
|
392 */ |
|
393 _displayRequestHeader: function NP__displayRequestHeader() |
|
394 { |
|
395 let request = this.httpActivity.request; |
|
396 let requestTime = new Date(this.httpActivity.startedDateTime); |
|
397 |
|
398 this._appendTextNode("headUrl", request.url); |
|
399 this._appendTextNode("headMethod", request.method); |
|
400 this._appendTextNode("requestHeadersInfo", |
|
401 l10n.timestampString(requestTime)); |
|
402 |
|
403 this._appendList("requestHeadersContent", request.headers, true); |
|
404 |
|
405 if (request.cookies.length > 0) { |
|
406 this._displayNode("requestCookie"); |
|
407 this._appendList("requestCookieContent", request.cookies); |
|
408 } |
|
409 }, |
|
410 |
|
411 /** |
|
412 * Displays the request body section of the NetworkPanel and set the request |
|
413 * body content on the NetworkPanel. |
|
414 * |
|
415 * @returns void |
|
416 */ |
|
417 _displayRequestBody: function NP__displayRequestBody() |
|
418 { |
|
419 let postData = this.httpActivity.request.postData; |
|
420 this._displayNode("requestBody"); |
|
421 this._appendTextNode("requestBodyContent", postData.text); |
|
422 }, |
|
423 |
|
424 /* |
|
425 * Displays the `sent form data` section. Parses the request header for the |
|
426 * submitted form data displays it inside of the `sent form data` section. |
|
427 * |
|
428 * @returns void |
|
429 */ |
|
430 _displayRequestForm: function NP__processRequestForm() |
|
431 { |
|
432 let postData = this.httpActivity.request.postData.text; |
|
433 let requestBodyLines = postData.split("\n"); |
|
434 let formData = requestBodyLines[requestBodyLines.length - 1]. |
|
435 replace(/\+/g, " ").split("&"); |
|
436 |
|
437 function unescapeText(aText) |
|
438 { |
|
439 try { |
|
440 return decodeURIComponent(aText); |
|
441 } |
|
442 catch (ex) { |
|
443 return decodeURIComponent(unescape(aText)); |
|
444 } |
|
445 } |
|
446 |
|
447 let formDataArray = []; |
|
448 for (let i = 0; i < formData.length; i++) { |
|
449 let data = formData[i]; |
|
450 let idx = data.indexOf("="); |
|
451 let key = data.substring(0, idx); |
|
452 let value = data.substring(idx + 1); |
|
453 formDataArray.push({ |
|
454 name: unescapeText(key), |
|
455 value: unescapeText(value) |
|
456 }); |
|
457 } |
|
458 |
|
459 this._appendList("requestFormDataContent", formDataArray); |
|
460 this._displayNode("requestFormData"); |
|
461 }, |
|
462 |
|
463 /** |
|
464 * Displays the response section of the NetworkPanel, sets the response status, |
|
465 * the duration between the start of the request and the receiving of the |
|
466 * response header as well as the response header content on the the NetworkPanel. |
|
467 * |
|
468 * @returns void |
|
469 */ |
|
470 _displayResponseHeader: function NP__displayResponseHeader() |
|
471 { |
|
472 let timing = this.httpActivity.timings; |
|
473 let response = this.httpActivity.response; |
|
474 |
|
475 this._appendTextNode("headStatus", |
|
476 [response.httpVersion, response.status, |
|
477 response.statusText].join(" ")); |
|
478 |
|
479 // Calculate how much time it took from the request start, until the |
|
480 // response started to be received. |
|
481 let deltaDuration = 0; |
|
482 ["dns", "connect", "send", "wait"].forEach(function (aValue) { |
|
483 let ms = timing[aValue]; |
|
484 if (ms > -1) { |
|
485 deltaDuration += ms; |
|
486 } |
|
487 }); |
|
488 |
|
489 this._appendTextNode("responseHeadersInfo", |
|
490 this._format("durationMS", [deltaDuration])); |
|
491 |
|
492 this._displayNode("responseContainer"); |
|
493 this._appendList("responseHeadersContent", response.headers, true); |
|
494 |
|
495 if (response.cookies.length > 0) { |
|
496 this._displayNode("responseCookie"); |
|
497 this._appendList("responseCookieContent", response.cookies); |
|
498 } |
|
499 }, |
|
500 |
|
501 /** |
|
502 * Displays the respones image section, sets the source of the image displayed |
|
503 * in the image response section to the request URL and the duration between |
|
504 * the receiving of the response header and the end of the request. Once the |
|
505 * image is loaded, the size of the requested image is set. |
|
506 * |
|
507 * @returns void |
|
508 */ |
|
509 _displayResponseImage: function NP__displayResponseImage() |
|
510 { |
|
511 let self = this; |
|
512 let timing = this.httpActivity.timings; |
|
513 let request = this.httpActivity.request; |
|
514 let response = this.httpActivity.response; |
|
515 let cached = ""; |
|
516 |
|
517 if (this._isResponseCached) { |
|
518 cached = "Cached"; |
|
519 } |
|
520 |
|
521 let imageNode = this.document.getElementById("responseImage" + |
|
522 cached + "Node"); |
|
523 |
|
524 let text = response.content.text; |
|
525 if (typeof text == "object" && text.type == "longString") { |
|
526 this._showResponseBodyFetchLink(); |
|
527 } |
|
528 else { |
|
529 imageNode.setAttribute("src", |
|
530 "data:" + this.contentType + ";base64," + text); |
|
531 } |
|
532 |
|
533 // This function is called to set the imageInfo. |
|
534 function setImageInfo() { |
|
535 self._appendTextNode("responseImage" + cached + "Info", |
|
536 self._format("imageSizeDeltaDurationMS", |
|
537 [ imageNode.width, imageNode.height, timing.receive ] |
|
538 ) |
|
539 ); |
|
540 } |
|
541 |
|
542 // Check if the image is already loaded. |
|
543 if (imageNode.width != 0) { |
|
544 setImageInfo(); |
|
545 } |
|
546 else { |
|
547 // Image is not loaded yet therefore add a load event. |
|
548 imageNode.addEventListener("load", function imageNodeLoad() { |
|
549 imageNode.removeEventListener("load", imageNodeLoad, false); |
|
550 setImageInfo(); |
|
551 }, false); |
|
552 } |
|
553 |
|
554 this._displayNode("responseImage" + cached); |
|
555 }, |
|
556 |
|
557 /** |
|
558 * Displays the response body section, sets the the duration between |
|
559 * the receiving of the response header and the end of the request as well as |
|
560 * the content of the response body on the NetworkPanel. |
|
561 * |
|
562 * @returns void |
|
563 */ |
|
564 _displayResponseBody: function NP__displayResponseBody() |
|
565 { |
|
566 let timing = this.httpActivity.timings; |
|
567 let response = this.httpActivity.response; |
|
568 let cached = this._isResponseCached ? "Cached" : ""; |
|
569 |
|
570 this._appendTextNode("responseBody" + cached + "Info", |
|
571 this._format("durationMS", [timing.receive])); |
|
572 |
|
573 this._displayNode("responseBody" + cached); |
|
574 |
|
575 let text = response.content.text; |
|
576 if (typeof text == "object") { |
|
577 text = text.initial; |
|
578 this._showResponseBodyFetchLink(); |
|
579 } |
|
580 |
|
581 this._appendTextNode("responseBody" + cached + "Content", text); |
|
582 }, |
|
583 |
|
584 /** |
|
585 * Show the "fetch response body" link. |
|
586 * @private |
|
587 */ |
|
588 _showResponseBodyFetchLink: function NP__showResponseBodyFetchLink() |
|
589 { |
|
590 let content = this.httpActivity.response.content; |
|
591 |
|
592 let elem = this._appendTextNode("responseBodyFetchLink", |
|
593 this._format("fetchRemainingResponseContentLink", |
|
594 [content.text.length - content.text.initial.length])); |
|
595 |
|
596 elem.style.display = "block"; |
|
597 elem.addEventListener("mousedown", this._responseBodyFetch); |
|
598 }, |
|
599 |
|
600 /** |
|
601 * Click event handler for the link that allows users to fetch the remaining |
|
602 * response body. |
|
603 * |
|
604 * @private |
|
605 * @param nsIDOMEvent aEvent |
|
606 */ |
|
607 _responseBodyFetch: function NP__responseBodyFetch(aEvent) |
|
608 { |
|
609 aEvent.target.style.display = "none"; |
|
610 aEvent.target.removeEventListener("mousedown", this._responseBodyFetch); |
|
611 |
|
612 let content = this.httpActivity.response.content; |
|
613 let longString = this.webconsole.webConsoleClient.longString(content.text); |
|
614 longString.substring(longString.initial.length, longString.length, |
|
615 function NP__onLongStringSubstring(aResponse) |
|
616 { |
|
617 if (aResponse.error) { |
|
618 Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error); |
|
619 return; |
|
620 } |
|
621 |
|
622 content.text = content.text.initial + aResponse.substring; |
|
623 let cached = this._isResponseCached ? "Cached" : ""; |
|
624 |
|
625 if (this._responseIsImage) { |
|
626 let imageNode = this.document.getElementById("responseImage" + |
|
627 cached + "Node"); |
|
628 imageNode.src = |
|
629 "data:" + this.contentType + ";base64," + content.text; |
|
630 } |
|
631 else { |
|
632 this._appendTextNode("responseBody" + cached + "Content", |
|
633 aResponse.substring); |
|
634 } |
|
635 }.bind(this)); |
|
636 }, |
|
637 |
|
638 /** |
|
639 * Displays the `Unknown Content-Type hint` and sets the duration between the |
|
640 * receiving of the response header on the NetworkPanel. |
|
641 * |
|
642 * @returns void |
|
643 */ |
|
644 _displayResponseBodyUnknownType: function NP__displayResponseBodyUnknownType() |
|
645 { |
|
646 let timing = this.httpActivity.timings; |
|
647 |
|
648 this._displayNode("responseBodyUnknownType"); |
|
649 this._appendTextNode("responseBodyUnknownTypeInfo", |
|
650 this._format("durationMS", [timing.receive])); |
|
651 |
|
652 this._appendTextNode("responseBodyUnknownTypeContent", |
|
653 this._format("responseBodyUnableToDisplay.content", [this.contentType])); |
|
654 }, |
|
655 |
|
656 /** |
|
657 * Displays the `no response body` section and sets the the duration between |
|
658 * the receiving of the response header and the end of the request. |
|
659 * |
|
660 * @returns void |
|
661 */ |
|
662 _displayNoResponseBody: function NP_displayNoResponseBody() |
|
663 { |
|
664 let timing = this.httpActivity.timings; |
|
665 |
|
666 this._displayNode("responseNoBody"); |
|
667 this._appendTextNode("responseNoBodyInfo", |
|
668 this._format("durationMS", [timing.receive])); |
|
669 }, |
|
670 |
|
671 /** |
|
672 * Updates the content of the NetworkPanel's iframe. |
|
673 * |
|
674 * @returns void |
|
675 */ |
|
676 update: function NP_update() |
|
677 { |
|
678 if (!this.document || this.document.readyState != "complete") { |
|
679 return; |
|
680 } |
|
681 |
|
682 let updates = this.httpActivity.updates; |
|
683 let timing = this.httpActivity.timings; |
|
684 let request = this.httpActivity.request; |
|
685 let response = this.httpActivity.response; |
|
686 |
|
687 switch (this._state) { |
|
688 case this._INIT: |
|
689 this._displayRequestHeader(); |
|
690 this._state = this._DISPLAYED_REQUEST_HEADER; |
|
691 // FALL THROUGH |
|
692 |
|
693 case this._DISPLAYED_REQUEST_HEADER: |
|
694 // Process the request body if there is one. |
|
695 if (!this.httpActivity.discardRequestBody && request.postData.text) { |
|
696 this._updateRequestBody(); |
|
697 this._state = this._DISPLAYED_REQUEST_BODY; |
|
698 } |
|
699 // FALL THROUGH |
|
700 |
|
701 case this._DISPLAYED_REQUEST_BODY: |
|
702 if (!response.headers.length || !Object.keys(timing).length) { |
|
703 break; |
|
704 } |
|
705 this._displayResponseHeader(); |
|
706 this._state = this._DISPLAYED_RESPONSE_HEADER; |
|
707 // FALL THROUGH |
|
708 |
|
709 case this._DISPLAYED_RESPONSE_HEADER: |
|
710 if (updates.indexOf("responseContent") == -1 || |
|
711 updates.indexOf("eventTimings") == -1) { |
|
712 break; |
|
713 } |
|
714 |
|
715 this._state = this._TRANSITION_CLOSED; |
|
716 if (this.httpActivity.discardResponseBody) { |
|
717 break; |
|
718 } |
|
719 |
|
720 if (!response.content || !response.content.text) { |
|
721 this._displayNoResponseBody(); |
|
722 } |
|
723 else if (this._responseIsImage) { |
|
724 this._displayResponseImage(); |
|
725 } |
|
726 else if (!this._isResponseBodyTextData) { |
|
727 this._displayResponseBodyUnknownType(); |
|
728 } |
|
729 else if (response.content.text) { |
|
730 this._displayResponseBody(); |
|
731 } |
|
732 break; |
|
733 } |
|
734 |
|
735 if (this._onUpdate) { |
|
736 this._onUpdate(); |
|
737 } |
|
738 }, |
|
739 |
|
740 /** |
|
741 * Update the panel to hold the current information we have about the request |
|
742 * body. |
|
743 * @private |
|
744 */ |
|
745 _updateRequestBody: function NP__updateRequestBody() |
|
746 { |
|
747 let postData = this.httpActivity.request.postData; |
|
748 if (typeof postData.text == "object" && postData.text.type == "longString") { |
|
749 let elem = this._appendTextNode("requestBodyFetchLink", |
|
750 this._format("fetchRemainingRequestContentLink", |
|
751 [postData.text.length - postData.text.initial.length])); |
|
752 |
|
753 elem.style.display = "block"; |
|
754 elem.addEventListener("mousedown", this._requestBodyFetch); |
|
755 return; |
|
756 } |
|
757 |
|
758 // Check if we send some form data. If so, display the form data special. |
|
759 if (this._isRequestBodyFormData) { |
|
760 this._displayRequestForm(); |
|
761 } |
|
762 else { |
|
763 this._displayRequestBody(); |
|
764 } |
|
765 }, |
|
766 |
|
767 /** |
|
768 * Click event handler for the link that allows users to fetch the remaining |
|
769 * request body. |
|
770 * |
|
771 * @private |
|
772 * @param nsIDOMEvent aEvent |
|
773 */ |
|
774 _requestBodyFetch: function NP__requestBodyFetch(aEvent) |
|
775 { |
|
776 aEvent.target.style.display = "none"; |
|
777 aEvent.target.removeEventListener("mousedown", this._responseBodyFetch); |
|
778 |
|
779 let postData = this.httpActivity.request.postData; |
|
780 let longString = this.webconsole.webConsoleClient.longString(postData.text); |
|
781 longString.substring(longString.initial.length, longString.length, |
|
782 function NP__onLongStringSubstring(aResponse) |
|
783 { |
|
784 if (aResponse.error) { |
|
785 Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error); |
|
786 return; |
|
787 } |
|
788 |
|
789 postData.text = postData.text.initial + aResponse.substring; |
|
790 this._updateRequestBody(); |
|
791 }.bind(this)); |
|
792 }, |
|
793 }; |
|
794 |
|
795 /** |
|
796 * Creates a DOMNode and sets all the attributes of aAttributes on the created |
|
797 * element. |
|
798 * |
|
799 * @param nsIDOMDocument aDocument |
|
800 * Document to create the new DOMNode. |
|
801 * @param string aTag |
|
802 * Name of the tag for the DOMNode. |
|
803 * @param object aAttributes |
|
804 * Attributes set on the created DOMNode. |
|
805 * |
|
806 * @returns nsIDOMNode |
|
807 */ |
|
808 function createElement(aDocument, aTag, aAttributes) |
|
809 { |
|
810 let node = aDocument.createElement(aTag); |
|
811 if (aAttributes) { |
|
812 for (let attr in aAttributes) { |
|
813 node.setAttribute(attr, aAttributes[attr]); |
|
814 } |
|
815 } |
|
816 return node; |
|
817 } |
|
818 |
|
819 /** |
|
820 * Creates a new DOMNode and appends it to aParent. |
|
821 * |
|
822 * @param nsIDOMNode aParent |
|
823 * A parent node to append the created element. |
|
824 * @param string aTag |
|
825 * Name of the tag for the DOMNode. |
|
826 * @param object aAttributes |
|
827 * Attributes set on the created DOMNode. |
|
828 * |
|
829 * @returns nsIDOMNode |
|
830 */ |
|
831 function createAndAppendElement(aParent, aTag, aAttributes) |
|
832 { |
|
833 let node = createElement(aParent.ownerDocument, aTag, aAttributes); |
|
834 aParent.appendChild(node); |
|
835 return node; |
|
836 } |