response-tab.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const React = require("devtools/client/shared/vendor/react");
  6. // Reps
  7. const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
  8. const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
  9. const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
  10. // Network
  11. const SizeLimit = React.createFactory(require("./size-limit"));
  12. const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
  13. const Spinner = React.createFactory(require("./spinner"));
  14. const Json = require("../utils/json");
  15. const NetUtils = require("../utils/net");
  16. // Shortcuts
  17. const DOM = React.DOM;
  18. const PropTypes = React.PropTypes;
  19. /**
  20. * This template represents 'Response' tab displayed when the user
  21. * expands network log in the Console panel. It's responsible for
  22. * rendering HTTP response body.
  23. *
  24. * In case of supported response mime-type (e.g. application/json,
  25. * text/xml, etc.), the response is parsed using appropriate parser
  26. * and rendered accordingly.
  27. */
  28. var ResponseTab = React.createClass({
  29. propTypes: {
  30. data: PropTypes.shape({
  31. request: PropTypes.object.isRequired,
  32. response: PropTypes.object.isRequired
  33. }),
  34. actions: PropTypes.object.isRequired
  35. },
  36. displayName: "ResponseTab",
  37. // Response Types
  38. isJson(content) {
  39. if (isLongString(content.text)) {
  40. return false;
  41. }
  42. return Json.isJSON(content.mimeType, content.text);
  43. },
  44. parseJson(file) {
  45. let content = file.response.content;
  46. if (isLongString(content.text)) {
  47. return null;
  48. }
  49. let jsonString = new String(content.text);
  50. return Json.parseJSONString(jsonString);
  51. },
  52. isImage(content) {
  53. if (isLongString(content.text)) {
  54. return false;
  55. }
  56. return NetUtils.isImage(content.mimeType);
  57. },
  58. isXml(content) {
  59. if (isLongString(content.text)) {
  60. return false;
  61. }
  62. return NetUtils.isHTML(content.mimeType);
  63. },
  64. parseXml(file) {
  65. let content = file.response.content;
  66. if (isLongString(content.text)) {
  67. return null;
  68. }
  69. return NetUtils.parseXml(content);
  70. },
  71. // Rendering
  72. renderJson(file) {
  73. let content = file.response.content;
  74. if (!this.isJson(content)) {
  75. return null;
  76. }
  77. let json = this.parseJson(file);
  78. if (!json) {
  79. return null;
  80. }
  81. return {
  82. key: "json",
  83. content: TreeView({
  84. columns: [{id: "value"}],
  85. object: json,
  86. mode: "tiny",
  87. renderValue: props => Rep(Object.assign({}, props, {
  88. cropLimit: 50,
  89. })),
  90. }),
  91. name: Locale.$STR("jsonScopeName")
  92. };
  93. },
  94. renderImage(file) {
  95. let content = file.response.content;
  96. if (!this.isImage(content)) {
  97. return null;
  98. }
  99. let dataUri = "data:" + content.mimeType + ";base64," + content.text;
  100. return {
  101. key: "image",
  102. content: DOM.img({src: dataUri}),
  103. name: Locale.$STR("netRequest.image")
  104. };
  105. },
  106. renderXml(file) {
  107. let content = file.response.content;
  108. if (!this.isXml(content)) {
  109. return null;
  110. }
  111. let doc = this.parseXml(file);
  112. if (!doc) {
  113. return null;
  114. }
  115. // Proper component for rendering XML should be used (see bug 1247392)
  116. return null;
  117. },
  118. /**
  119. * If full response text is available, let's try to parse and
  120. * present nicely according to the underlying format.
  121. */
  122. renderFormattedResponse(file) {
  123. let content = file.response.content;
  124. if (typeof content.text == "object") {
  125. return null;
  126. }
  127. let group = this.renderJson(file);
  128. if (group) {
  129. return group;
  130. }
  131. group = this.renderImage(file);
  132. if (group) {
  133. return group;
  134. }
  135. group = this.renderXml(file);
  136. if (group) {
  137. return group;
  138. }
  139. },
  140. renderRawResponse(file) {
  141. let group;
  142. let content = file.response.content;
  143. // The response might reached the limit, so check if we are
  144. // dealing with a long string.
  145. if (typeof content.text == "object") {
  146. group = {
  147. key: "raw-longstring",
  148. name: Locale.$STR("netRequest.rawData"),
  149. content: DOM.div({className: "netInfoResponseContent"},
  150. content.text.initial,
  151. SizeLimit({
  152. actions: this.props.actions,
  153. data: content,
  154. message: Locale.$STR("netRequest.sizeLimitMessage"),
  155. link: Locale.$STR("netRequest.sizeLimitMessageLink")
  156. })
  157. )
  158. };
  159. } else {
  160. group = {
  161. key: "raw",
  162. name: Locale.$STR("netRequest.rawData"),
  163. content: DOM.div({className: "netInfoResponseContent"},
  164. content.text
  165. )
  166. };
  167. }
  168. return group;
  169. },
  170. componentDidMount() {
  171. let { actions, data: file } = this.props;
  172. let content = file.response.content;
  173. if (!content || typeof (content.text) == "undefined") {
  174. // TODO: use async action objects as soon as Redux is in place
  175. actions.requestData("responseContent");
  176. }
  177. },
  178. /**
  179. * The response panel displays two groups:
  180. *
  181. * 1) Formatted response (in case of supported format, e.g. JSON, XML, etc.)
  182. * 2) Raw response data (always displayed if not discarded)
  183. */
  184. render() {
  185. let { actions, data: file } = this.props;
  186. // If response bodies are discarded (not collected) let's just
  187. // display a info message indicating what to do to collect even
  188. // response bodies.
  189. if (file.discardResponseBody) {
  190. return DOM.span({className: "netInfoBodiesDiscarded"},
  191. Locale.$STR("netRequest.responseBodyDiscarded")
  192. );
  193. }
  194. // Request for the response content is done only if the response
  195. // is not fetched yet - i.e. the `content.text` is undefined.
  196. // Empty content.text` can also be a valid response either
  197. // empty or not available yet.
  198. let content = file.response.content;
  199. if (!content || typeof (content.text) == "undefined") {
  200. return (
  201. Spinner()
  202. );
  203. }
  204. // Render response body data. The right representation of the data
  205. // is picked according to the content type.
  206. let groups = [];
  207. groups.push(this.renderFormattedResponse(file));
  208. groups.push(this.renderRawResponse(file));
  209. // Filter out empty groups.
  210. groups = groups.filter(group => group);
  211. // The raw response is collapsed by default if a nice formatted
  212. // version is available.
  213. if (groups.length > 1) {
  214. groups[1].open = false;
  215. }
  216. return (
  217. DOM.div({className: "responseTabBox"},
  218. DOM.div({className: "panelContent"},
  219. NetInfoGroupList({
  220. groups: groups
  221. })
  222. )
  223. )
  224. );
  225. }
  226. });
  227. // Helpers
  228. function isLongString(text) {
  229. return typeof text == "object";
  230. }
  231. // Exports from this module
  232. module.exports = ResponseTab;