summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/net/components
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webconsole/net/components')
-rw-r--r--devtools/client/webconsole/net/components/cookies-tab.js75
-rw-r--r--devtools/client/webconsole/net/components/headers-tab.js79
-rw-r--r--devtools/client/webconsole/net/components/moz.build25
-rw-r--r--devtools/client/webconsole/net/components/net-info-body.css112
-rw-r--r--devtools/client/webconsole/net/components/net-info-body.js179
-rw-r--r--devtools/client/webconsole/net/components/net-info-group-list.js47
-rw-r--r--devtools/client/webconsole/net/components/net-info-group.css80
-rw-r--r--devtools/client/webconsole/net/components/net-info-group.js80
-rw-r--r--devtools/client/webconsole/net/components/net-info-params.css23
-rw-r--r--devtools/client/webconsole/net/components/net-info-params.js58
-rw-r--r--devtools/client/webconsole/net/components/params-tab.js41
-rw-r--r--devtools/client/webconsole/net/components/post-tab.js279
-rw-r--r--devtools/client/webconsole/net/components/response-tab.css21
-rw-r--r--devtools/client/webconsole/net/components/response-tab.js277
-rw-r--r--devtools/client/webconsole/net/components/size-limit.css15
-rw-r--r--devtools/client/webconsole/net/components/size-limit.js62
-rw-r--r--devtools/client/webconsole/net/components/spinner.js26
-rw-r--r--devtools/client/webconsole/net/components/stacktrace-tab.js29
18 files changed, 1508 insertions, 0 deletions
diff --git a/devtools/client/webconsole/net/components/cookies-tab.js b/devtools/client/webconsole/net/components/cookies-tab.js
new file mode 100644
index 000000000..d76414679
--- /dev/null
+++ b/devtools/client/webconsole/net/components/cookies-tab.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
+const Spinner = React.createFactory(require("./spinner"));
+
+// Shortcuts
+const DOM = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This template represents 'Cookies' tab displayed when the user
+ * expands network log in the Console panel. It's responsible for rendering
+ * sent and received cookies.
+ */
+var CookiesTab = React.createClass({
+ propTypes: {
+ actions: PropTypes.shape({
+ requestData: PropTypes.func.isRequired
+ }),
+ data: PropTypes.object.isRequired,
+ },
+
+ displayName: "CookiesTab",
+
+ componentDidMount() {
+ let { actions, data } = this.props;
+ let requestCookies = data.request.cookies;
+ let responseCookies = data.response.cookies;
+
+ // TODO: use async action objects as soon as Redux is in place
+ if (!requestCookies || !requestCookies.length) {
+ actions.requestData("requestCookies");
+ }
+
+ if (!responseCookies || !responseCookies.length) {
+ actions.requestData("responseCookies");
+ }
+ },
+
+ render() {
+ let { actions, data: file } = this.props;
+ let requestCookies = file.request.cookies;
+ let responseCookies = file.response.cookies;
+
+ // The cookie panel displays two groups of cookies:
+ // 1) Response Cookies
+ // 2) Request Cookies
+ let groups = [{
+ key: "responseCookies",
+ name: Locale.$STR("responseCookies"),
+ params: responseCookies
+ }, {
+ key: "requestCookies",
+ name: Locale.$STR("requestCookies"),
+ params: requestCookies
+ }];
+
+ return (
+ DOM.div({className: "cookiesTabBox"},
+ DOM.div({className: "panelContent"},
+ NetInfoGroupList({
+ groups: groups
+ })
+ )
+ )
+ );
+ }
+});
+
+// Exports from this module
+module.exports = CookiesTab;
diff --git a/devtools/client/webconsole/net/components/headers-tab.js b/devtools/client/webconsole/net/components/headers-tab.js
new file mode 100644
index 000000000..2eca3fd2f
--- /dev/null
+++ b/devtools/client/webconsole/net/components/headers-tab.js
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
+const Spinner = React.createFactory(require("./spinner"));
+
+// Shortcuts
+const DOM = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This template represents 'Headers' tab displayed when the user
+ * expands network log in the Console panel. It's responsible for rendering
+ * request and response HTTP headers.
+ */
+var HeadersTab = React.createClass({
+ propTypes: {
+ actions: PropTypes.shape({
+ requestData: PropTypes.func.isRequired
+ }),
+ data: PropTypes.object.isRequired,
+ },
+
+ displayName: "HeadersTab",
+
+ componentDidMount() {
+ let { actions, data } = this.props;
+ let requestHeaders = data.request.headers;
+ let responseHeaders = data.response.headers;
+
+ // Request headers if they are not available yet.
+ // TODO: use async action objects as soon as Redux is in place
+ if (!requestHeaders) {
+ actions.requestData("requestHeaders");
+ }
+
+ if (!responseHeaders) {
+ actions.requestData("responseHeaders");
+ }
+ },
+
+ render() {
+ let { data } = this.props;
+ let requestHeaders = data.request.headers;
+ let responseHeaders = data.response.headers;
+
+ // TODO: Another groups to implement:
+ // 1) Cached Headers
+ // 2) Headers from upload stream
+ let groups = [{
+ key: "responseHeaders",
+ name: Locale.$STR("responseHeaders"),
+ params: responseHeaders
+ }, {
+ key: "requestHeaders",
+ name: Locale.$STR("requestHeaders"),
+ params: requestHeaders
+ }];
+
+ // If response headers are not available yet, display a spinner
+ if (!responseHeaders || !responseHeaders.length) {
+ groups[0].content = Spinner();
+ }
+
+ return (
+ DOM.div({className: "headersTabBox"},
+ DOM.div({className: "panelContent"},
+ NetInfoGroupList({groups: groups})
+ )
+ )
+ );
+ }
+});
+
+// Exports from this module
+module.exports = HeadersTab;
diff --git a/devtools/client/webconsole/net/components/moz.build b/devtools/client/webconsole/net/components/moz.build
new file mode 100644
index 000000000..0053de780
--- /dev/null
+++ b/devtools/client/webconsole/net/components/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'cookies-tab.js',
+ 'headers-tab.js',
+ 'net-info-body.css',
+ 'net-info-body.js',
+ 'net-info-group-list.js',
+ 'net-info-group.css',
+ 'net-info-group.js',
+ 'net-info-params.css',
+ 'net-info-params.js',
+ 'params-tab.js',
+ 'post-tab.js',
+ 'response-tab.css',
+ 'response-tab.js',
+ 'size-limit.css',
+ 'size-limit.js',
+ 'spinner.js',
+ 'stacktrace-tab.js',
+)
diff --git a/devtools/client/webconsole/net/components/net-info-body.css b/devtools/client/webconsole/net/components/net-info-body.css
new file mode 100644
index 000000000..2d0bac70e
--- /dev/null
+++ b/devtools/client/webconsole/net/components/net-info-body.css
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/******************************************************************************/
+/* Network Info Body */
+
+.netInfoBody {
+ margin: 10px 0 0 0;
+ width: 100%;
+ cursor: default;
+ display: block;
+}
+
+.netInfoBody *:focus {
+ outline: 0 !important;
+}
+
+.netInfoBody .panelContent {
+ word-break: break-all;
+}
+
+/******************************************************************************/
+/* Network Info Body Tabs */
+
+.netInfoBody > .tabs {
+ background-color: transparent;
+ background-image: none;
+ height: 100%;
+}
+
+.netInfoBody > .tabs .tabs-navigation {
+ border-bottom-color: var(--net-border);
+ background-color: transparent;
+ text-decoration: none;
+ padding-top: 3px;
+ padding-left: 7px;
+ padding-bottom: 1px;
+ border-bottom: 1px solid var(--net-border);
+}
+
+.netInfoBody > .tabs .tabs-menu {
+ display: table;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+/* This is the trick that makes the tab bottom border invisible */
+.netInfoBody > .tabs .tabs-menu-item {
+ position: relative;
+ bottom: -2px;
+ float: left;
+}
+
+.netInfoBody > .tabs .tabs-menu-item a {
+ display: block;
+ border: 1px solid transparent;
+ text-decoration: none;
+ padding: 5px 8px 4px 8px;;
+ font-weight: bold;
+ color: var(--theme-body-color);
+ border-radius: 4px 4px 0 0;
+}
+
+.netInfoBody > .tabs .tab-panel {
+ background-color: var(--theme-body-background);
+ border: 1px solid transparent;
+ border-top: none;
+ padding: 10px;
+ overflow: auto;
+ height: calc(100% - 31px); /* minus the height of the tab bar */
+}
+
+.netInfoBody > .tabs .tab-panel > div,
+.netInfoBody > .tabs .tab-panel > div > div {
+ height: 100%;
+}
+
+.netInfoBody > .tabs .tabs-menu-item.is-active a,
+.netInfoBody > .tabs .tabs-menu-item.is-active a:focus,
+.netInfoBody > .tabs .tabs-menu-item.is-active:hover a {
+ background-color: var(--theme-body-background);
+ border: 1px solid transparent;
+ border-bottom-color: var(--theme-highlight-bluegrey);
+ color: var(--theme-highlight-bluegrey);
+}
+
+.netInfoBody > .tabs .tabs-menu-item:hover a {
+ border: 1px solid transparent;
+ border-bottom: 1px solid var(--net-border);
+ background-color: var(--theme-body-background);
+}
+
+
+/******************************************************************************/
+/* Themes */
+
+.theme-firebug .netInfoBody > .tabs .tab-panel {
+ border-color: var(--net-border);
+}
+
+.theme-firebug .netInfoBody > .tabs .tabs-menu-item.is-active a,
+.theme-firebug .netInfoBody > .tabs .tabs-menu-item.is-active:hover a,
+.theme-firebug .netInfoBody > .tabs .tabs-menu-item.is-active a:focus {
+ border: 1px solid var(--net-border);
+ border-bottom-color: transparent;
+}
+
+.theme-firebug .netInfoBody > .tabs .tabs-menu-item:hover a {
+ border-bottom-color: transparent;
+}
diff --git a/devtools/client/webconsole/net/components/net-info-body.js b/devtools/client/webconsole/net/components/net-info-body.js
new file mode 100644
index 000000000..c5eccd458
--- /dev/null
+++ b/devtools/client/webconsole/net/components/net-info-body.js
@@ -0,0 +1,179 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const { Tabs, TabPanel } = createFactories(require("devtools/client/shared/components/tabs/tabs"));
+
+// Network
+const HeadersTab = React.createFactory(require("./headers-tab"));
+const ResponseTab = React.createFactory(require("./response-tab"));
+const ParamsTab = React.createFactory(require("./params-tab"));
+const CookiesTab = React.createFactory(require("./cookies-tab"));
+const PostTab = React.createFactory(require("./post-tab"));
+const StackTraceTab = React.createFactory(require("./stacktrace-tab"));
+const NetUtils = require("../utils/net");
+
+// Shortcuts
+const PropTypes = React.PropTypes;
+
+/**
+ * This template renders the basic Network log info body. It's not
+ * visible by default, the user needs to expand the network log
+ * to see it.
+ *
+ * This is the set of tabs displaying details about network events:
+ * 1) Headers - request and response headers
+ * 2) Params - URL parameters
+ * 3) Response - response body
+ * 4) Cookies - request and response cookies
+ * 5) Post - posted data
+ */
+var NetInfoBody = React.createClass({
+ propTypes: {
+ tabActive: PropTypes.number.isRequired,
+ actions: PropTypes.object.isRequired,
+ data: PropTypes.shape({
+ request: PropTypes.object.isRequired,
+ response: PropTypes.object.isRequired
+ })
+ },
+
+ displayName: "NetInfoBody",
+
+ getDefaultProps() {
+ return {
+ tabActive: 0
+ };
+ },
+
+ getInitialState() {
+ return {
+ data: {
+ request: {},
+ response: {}
+ },
+ tabActive: this.props.tabActive,
+ };
+ },
+
+ onTabChanged(index) {
+ this.setState({tabActive: index});
+ },
+
+ hasCookies() {
+ let {request, response} = this.state.data;
+ return this.state.hasCookies ||
+ NetUtils.getHeaderValue(request.headers, "Cookie") ||
+ NetUtils.getHeaderValue(response.headers, "Set-Cookie");
+ },
+
+ hasStackTrace() {
+ let {cause} = this.state.data;
+ return cause && cause.stacktrace && cause.stacktrace.length > 0;
+ },
+
+ getTabPanels() {
+ let actions = this.props.actions;
+ let data = this.state.data;
+ let {request} = data;
+
+ // Flags for optional tabs. Some tabs are visible only if there
+ // are data to display.
+ let hasParams = request.queryString && request.queryString.length;
+ let hasPostData = request.bodySize > 0;
+
+ let panels = [];
+
+ // Headers tab
+ panels.push(
+ TabPanel({
+ className: "headers",
+ key: "headers",
+ title: Locale.$STR("netRequest.headers")},
+ HeadersTab({data: data, actions: actions})
+ )
+ );
+
+ // URL parameters tab
+ if (hasParams) {
+ panels.push(
+ TabPanel({
+ className: "params",
+ key: "params",
+ title: Locale.$STR("netRequest.params")},
+ ParamsTab({data: data, actions: actions})
+ )
+ );
+ }
+
+ // Posted data tab
+ if (hasPostData) {
+ panels.push(
+ TabPanel({
+ className: "post",
+ key: "post",
+ title: Locale.$STR("netRequest.post")},
+ PostTab({data: data, actions: actions})
+ )
+ );
+ }
+
+ // Response tab
+ panels.push(
+ TabPanel({className: "response", key: "response",
+ title: Locale.$STR("netRequest.response")},
+ ResponseTab({data: data, actions: actions})
+ )
+ );
+
+ // Cookies tab
+ if (this.hasCookies()) {
+ panels.push(
+ TabPanel({
+ className: "cookies",
+ key: "cookies",
+ title: Locale.$STR("netRequest.cookies")},
+ CookiesTab({
+ data: data,
+ actions: actions
+ })
+ )
+ );
+ }
+
+ // Stacktrace tab
+ if (this.hasStackTrace()) {
+ panels.push(
+ TabPanel({
+ className: "stacktrace-tab",
+ key: "stacktrace",
+ title: Locale.$STR("netRequest.callstack")},
+ StackTraceTab({
+ data: data,
+ actions: actions
+ })
+ )
+ );
+ }
+
+ return panels;
+ },
+
+ render() {
+ let tabActive = this.state.tabActive;
+ let tabPanels = this.getTabPanels();
+ return (
+ Tabs({
+ tabActive: tabActive,
+ onAfterChange: this.onTabChanged},
+ tabPanels
+ )
+ );
+ }
+});
+
+// Exports from this module
+module.exports = NetInfoBody;
diff --git a/devtools/client/webconsole/net/components/net-info-group-list.js b/devtools/client/webconsole/net/components/net-info-group-list.js
new file mode 100644
index 000000000..247a23bb7
--- /dev/null
+++ b/devtools/client/webconsole/net/components/net-info-group-list.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+const NetInfoGroup = React.createFactory(require("./net-info-group"));
+
+// Shortcuts
+const DOM = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This template is responsible for rendering sections/groups inside tabs.
+ * It's used e.g to display Response and Request headers as separate groups.
+ */
+var NetInfoGroupList = React.createClass({
+ propTypes: {
+ groups: PropTypes.array.isRequired,
+ },
+
+ displayName: "NetInfoGroupList",
+
+ render() {
+ let groups = this.props.groups;
+
+ // Filter out empty groups.
+ groups = groups.filter(group => {
+ return group && ((group.params && group.params.length) || group.content);
+ });
+
+ // Render groups
+ groups = groups.map(group => {
+ group.type = group.key;
+ return NetInfoGroup(group);
+ });
+
+ return (
+ DOM.div({className: "netInfoGroupList"},
+ groups
+ )
+ );
+ }
+});
+
+// Exports from this module
+module.exports = NetInfoGroupList;
diff --git a/devtools/client/webconsole/net/components/net-info-group.css b/devtools/client/webconsole/net/components/net-info-group.css
new file mode 100644
index 000000000..43800019f
--- /dev/null
+++ b/devtools/client/webconsole/net/components/net-info-group.css
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/******************************************************************************/
+/* Net Info Group */
+
+.netInfoBody .netInfoGroup {
+ padding-bottom: 6px;
+}
+
+/* Last group doesn't need bottom padding */
+.netInfoBody .netInfoGroup:last-child {
+ padding-bottom: 0;
+}
+
+.netInfoBody .netInfoGroup:last-child .netInfoGroupContent {
+ padding-bottom: 0;
+}
+
+.netInfoBody .netInfoGroupTitle {
+ cursor: pointer;
+ font-weight: bold;
+ -moz-user-select: none;
+ cursor: pointer;
+ padding-left: 3px;
+}
+
+.netInfoBody .netInfoGroupTwisty {
+ background-image: url("chrome://devtools/skin/images/controls.png");
+ background-size: 56px 28px;
+ background-position: 0 -14px;
+ background-repeat: no-repeat;
+ width: 14px;
+ height: 14px;
+ cursor: pointer;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.netInfoBody .netInfoGroup.opened .netInfoGroupTwisty {
+ background-position: -14px -14px;
+}
+
+/* Group content is expandable/collapsible by clicking on the title */
+.netInfoBody .netInfoGroupContent {
+ padding-top: 7px;
+ margin-top: 3px;
+ padding-bottom: 14px;
+ border-top: 1px solid var(--net-border);
+ display: none;
+}
+
+/* Toggle group visibility */
+.netInfoBody .netInfoGroup.opened .netInfoGroupContent {
+ display: block;
+}
+
+/******************************************************************************/
+/* Themes */
+
+.theme-dark .netInfoBody .netInfoGroup {
+ color: var(--theme-body-color);
+}
+
+.theme-dark .netInfoBody .netInfoGroup .netInfoGroupTwisty {
+ filter: invert(1);
+}
+
+/* Twisties */
+.theme-firebug .netInfoBody .netInfoGroup .netInfoGroupTwisty {
+ background-image: url("chrome://devtools/skin/images/firebug/twisty-closed-firebug.svg");
+ background-position: 0 2px;
+ background-size: 11px 11px;
+ width: 15px;
+}
+
+.theme-firebug .netInfoBody .netInfoGroup.opened .netInfoGroupTwisty {
+ background-image: url("chrome://devtools/skin/images/firebug/twisty-open-firebug.svg");
+}
diff --git a/devtools/client/webconsole/net/components/net-info-group.js b/devtools/client/webconsole/net/components/net-info-group.js
new file mode 100644
index 000000000..d9794652e
--- /dev/null
+++ b/devtools/client/webconsole/net/components/net-info-group.js
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+const NetInfoParams = React.createFactory(require("./net-info-params"));
+
+// Shortcuts
+const DOM = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This template represents a group of data within a tab. For example,
+ * Headers tab has two groups 'Request Headers' and 'Response Headers'
+ * The Response tab can also have two groups 'Raw Data' and 'JSON'
+ */
+var NetInfoGroup = React.createClass({
+ propTypes: {
+ type: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ params: PropTypes.array,
+ content: PropTypes.element,
+ open: PropTypes.bool
+ },
+
+ displayName: "NetInfoGroup",
+
+ getDefaultProps() {
+ return {
+ open: true,
+ };
+ },
+
+ getInitialState() {
+ return {
+ open: this.props.open,
+ };
+ },
+
+ onToggle(event) {
+ this.setState({
+ open: !this.state.open
+ });
+ },
+
+ render() {
+ let content = this.props.content;
+
+ if (!content && this.props.params) {
+ content = NetInfoParams({
+ params: this.props.params
+ });
+ }
+
+ let open = this.state.open;
+ let className = open ? "opened" : "";
+
+ return (
+ DOM.div({className: "netInfoGroup" + " " + className + " " +
+ this.props.type},
+ DOM.span({
+ className: "netInfoGroupTwisty",
+ onClick: this.onToggle
+ }),
+ DOM.span({
+ className: "netInfoGroupTitle",
+ onClick: this.onToggle},
+ this.props.name
+ ),
+ DOM.div({className: "netInfoGroupContent"},
+ content
+ )
+ )
+ );
+ }
+});
+
+// Exports from this module
+module.exports = NetInfoGroup;
diff --git a/devtools/client/webconsole/net/components/net-info-params.css b/devtools/client/webconsole/net/components/net-info-params.css
new file mode 100644
index 000000000..4ec7140f8
--- /dev/null
+++ b/devtools/client/webconsole/net/components/net-info-params.css
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/******************************************************************************/
+/* Net Info Params */
+
+.netInfoBody .netInfoParamName {
+ padding: 0 10px 0 0;
+ font-weight: bold;
+ vertical-align: top;
+ text-align: right;
+ white-space: nowrap;
+}
+
+.netInfoBody .netInfoParamValue {
+ width: 100%;
+ word-wrap: break-word;
+}
+
+.netInfoBody .netInfoParamValue > code {
+ font-family: var(--monospace-font-family);
+}
diff --git a/devtools/client/webconsole/net/components/net-info-params.js b/devtools/client/webconsole/net/components/net-info-params.js
new file mode 100644
index 000000000..573257b28
--- /dev/null
+++ b/devtools/client/webconsole/net/components/net-info-params.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+
+// Shortcuts
+const DOM = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This template renders list of parameters within a group.
+ * It's essentially a list of name + value pairs.
+ */
+var NetInfoParams = React.createClass({
+ displayName: "NetInfoParams",
+
+ propTypes: {
+ params: PropTypes.arrayOf(PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired
+ })).isRequired,
+ },
+
+ render() {
+ let params = this.props.params || [];
+
+ params.sort(function (a, b) {
+ return a.name > b.name ? 1 : -1;
+ });
+
+ let rows = [];
+ params.forEach((param, index) => {
+ rows.push(
+ DOM.tr({key: index},
+ DOM.td({className: "netInfoParamName"},
+ DOM.span({title: param.name}, param.name)
+ ),
+ DOM.td({className: "netInfoParamValue"},
+ DOM.code({}, param.value)
+ )
+ )
+ );
+ });
+
+ return (
+ DOM.table({cellPadding: 0, cellSpacing: 0},
+ DOM.tbody({},
+ rows
+ )
+ )
+ );
+ }
+});
+
+// Exports from this module
+module.exports = NetInfoParams;
diff --git a/devtools/client/webconsole/net/components/params-tab.js b/devtools/client/webconsole/net/components/params-tab.js
new file mode 100644
index 000000000..c3fefc669
--- /dev/null
+++ b/devtools/client/webconsole/net/components/params-tab.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+const NetInfoParams = React.createFactory(require("./net-info-params"));
+
+// Shortcuts
+const DOM = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This template represents 'Params' tab displayed when the user
+ * expands network log in the Console panel. It's responsible for
+ * displaying URL parameters (query string).
+ */
+var ParamsTab = React.createClass({
+ propTypes: {
+ data: PropTypes.shape({
+ request: PropTypes.object.isRequired
+ })
+ },
+
+ displayName: "ParamsTab",
+
+ render() {
+ let data = this.props.data;
+
+ return (
+ DOM.div({className: "paramsTabBox"},
+ DOM.div({className: "panelContent"},
+ NetInfoParams({params: data.request.queryString})
+ )
+ )
+ );
+ }
+});
+
+// Exports from this module
+module.exports = ParamsTab;
diff --git a/devtools/client/webconsole/net/components/post-tab.js b/devtools/client/webconsole/net/components/post-tab.js
new file mode 100644
index 000000000..6d06eb40b
--- /dev/null
+++ b/devtools/client/webconsole/net/components/post-tab.js
@@ -0,0 +1,279 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+
+// Reps
+const { createFactories, parseURLEncodedText } = require("devtools/client/shared/components/reps/rep-utils");
+const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
+const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
+
+// Network
+const NetInfoParams = React.createFactory(require("./net-info-params"));
+const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
+const Spinner = React.createFactory(require("./spinner"));
+const SizeLimit = React.createFactory(require("./size-limit"));
+const NetUtils = require("../utils/net");
+const Json = require("../utils/json");
+
+// Shortcuts
+const DOM = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This template represents 'Post' tab displayed when the user
+ * expands network log in the Console panel. It's responsible for
+ * displaying posted data (HTTP post body).
+ */
+var PostTab = React.createClass({
+ propTypes: {
+ data: PropTypes.shape({
+ request: PropTypes.object.isRequired
+ }),
+ actions: PropTypes.object.isRequired
+ },
+
+ displayName: "PostTab",
+
+ isJson(file) {
+ let text = file.request.postData.text;
+ let value = NetUtils.getHeaderValue(file.request.headers, "content-type");
+ return Json.isJSON(value, text);
+ },
+
+ parseJson(file) {
+ let postData = file.request.postData;
+ if (!postData) {
+ return null;
+ }
+
+ let jsonString = new String(postData.text);
+ return Json.parseJSONString(jsonString);
+ },
+
+ /**
+ * Render JSON post data as an expandable tree.
+ */
+ renderJson(file) {
+ let text = file.request.postData.text;
+ if (!text || isLongString(text)) {
+ return null;
+ }
+
+ if (!this.isJson(file)) {
+ return null;
+ }
+
+ let json = this.parseJson(file);
+ if (!json) {
+ return null;
+ }
+
+ return {
+ key: "json",
+ content: TreeView({
+ columns: [{id: "value"}],
+ object: json,
+ mode: "tiny",
+ renderValue: props => Rep(Object.assign({}, props, {
+ cropLimit: 50,
+ })),
+ }),
+ name: Locale.$STR("jsonScopeName")
+ };
+ },
+
+ parseXml(file) {
+ let text = file.request.postData.text;
+ if (isLongString(text)) {
+ return null;
+ }
+
+ return NetUtils.parseXml({
+ mimeType: NetUtils.getHeaderValue(file.request.headers, "content-type"),
+ text: text,
+ });
+ },
+
+ isXml(file) {
+ if (isLongString(file.request.postData.text)) {
+ return false;
+ }
+
+ let value = NetUtils.getHeaderValue(file.request.headers, "content-type");
+ if (!value) {
+ return false;
+ }
+
+ return NetUtils.isHTML(value);
+ },
+
+ renderXml(file) {
+ let text = file.request.postData.text;
+ if (!text || isLongString(text)) {
+ return null;
+ }
+
+ if (!this.isXml(file)) {
+ return null;
+ }
+
+ let doc = this.parseXml(file);
+ if (!doc) {
+ return null;
+ }
+
+ // Proper component for rendering XML should be used (see bug 1247392)
+ return null;
+ },
+
+ /**
+ * Multipart post data are parsed and nicely rendered
+ * as an expandable tree of individual parts.
+ */
+ renderMultiPart(file) {
+ let text = file.request.postData.text;
+ if (!text || isLongString(text)) {
+ return;
+ }
+
+ if (NetUtils.isMultiPartRequest(file)) {
+ // TODO: render multi part request (bug: 1247423)
+ }
+
+ return;
+ },
+
+ /**
+ * URL encoded post data are nicely rendered as a list
+ * of parameters.
+ */
+ renderUrlEncoded(file) {
+ let text = file.request.postData.text;
+ if (!text || isLongString(text)) {
+ return null;
+ }
+
+ if (!NetUtils.isURLEncodedRequest(file)) {
+ return null;
+ }
+
+ let lines = text.split("\n");
+ let params = parseURLEncodedText(lines[lines.length - 1]);
+
+ return {
+ key: "url-encoded",
+ content: NetInfoParams({params: params}),
+ name: Locale.$STR("netRequest.params")
+ };
+ },
+
+ renderRawData(file) {
+ let text = file.request.postData.text;
+
+ let group;
+
+ // The post body might reached the limit, so check if we are
+ // dealing with a long string.
+ if (typeof text == "object") {
+ group = {
+ key: "raw-longstring",
+ name: Locale.$STR("netRequest.rawData"),
+ content: DOM.div({className: "netInfoResponseContent"},
+ sanitize(text.initial),
+ SizeLimit({
+ actions: this.props.actions,
+ data: file.request.postData,
+ message: Locale.$STR("netRequest.sizeLimitMessage"),
+ link: Locale.$STR("netRequest.sizeLimitMessageLink")
+ })
+ )
+ };
+ } else {
+ group = {
+ key: "raw",
+ name: Locale.$STR("netRequest.rawData"),
+ content: DOM.div({className: "netInfoResponseContent"},
+ sanitize(text)
+ )
+ };
+ }
+
+ return group;
+ },
+
+ componentDidMount() {
+ let { actions, data: file } = this.props;
+
+ if (!file.request.postData) {
+ // TODO: use async action objects as soon as Redux is in place
+ actions.requestData("requestPostData");
+ }
+ },
+
+ render() {
+ let { actions, data: file } = this.props;
+
+ if (file.discardRequestBody) {
+ return DOM.span({className: "netInfoBodiesDiscarded"},
+ Locale.$STR("netRequest.requestBodyDiscarded")
+ );
+ }
+
+ if (!file.request.postData) {
+ return (
+ Spinner()
+ );
+ }
+
+ // Render post body data. The right representation of the data
+ // is picked according to the content type.
+ let groups = [];
+ groups.push(this.renderUrlEncoded(file));
+ // TODO: render multi part request (bug: 1247423)
+ // groups.push(this.renderMultiPart(file));
+ groups.push(this.renderJson(file));
+ groups.push(this.renderXml(file));
+ groups.push(this.renderRawData(file));
+
+ // Filter out empty groups.
+ groups = groups.filter(group => group);
+
+ // The raw response is collapsed by default if a nice formatted
+ // version is available.
+ if (groups.length > 1) {
+ groups[groups.length - 1].open = false;
+ }
+
+ return (
+ DOM.div({className: "postTabBox"},
+ DOM.div({className: "panelContent"},
+ NetInfoGroupList({
+ groups: groups
+ })
+ )
+ )
+ );
+ }
+});
+
+// Helpers
+
+/**
+ * Workaround for a "not well-formed" error that react
+ * reports when there's multipart data passed to render.
+ */
+function sanitize(text) {
+ text = JSON.stringify(text);
+ text = text.replace(/\\r\\n/g, "\r\n").replace(/\\"/g, "\"");
+ return text.slice(1, text.length - 1);
+}
+
+function isLongString(text) {
+ return typeof text == "object";
+}
+
+// Exports from this module
+module.exports = PostTab;
diff --git a/devtools/client/webconsole/net/components/response-tab.css b/devtools/client/webconsole/net/components/response-tab.css
new file mode 100644
index 000000000..e1c31fca4
--- /dev/null
+++ b/devtools/client/webconsole/net/components/response-tab.css
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/******************************************************************************/
+/* Response Tab */
+
+.netInfoBody .netInfoBodiesDiscarded {
+ font-style: italic;
+ color: gray;
+}
+
+.netInfoBody .netInfoResponseContent {
+ font-family: var(--monospace-font-family);
+ word-wrap: break-word;
+}
+
+.netInfoBody .responseTabBox img {
+ max-width: 300px;
+ max-height: 300px;
+}
diff --git a/devtools/client/webconsole/net/components/response-tab.js b/devtools/client/webconsole/net/components/response-tab.js
new file mode 100644
index 000000000..78d8b2f77
--- /dev/null
+++ b/devtools/client/webconsole/net/components/response-tab.js
@@ -0,0 +1,277 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+
+// Reps
+const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
+const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
+
+// Network
+const SizeLimit = React.createFactory(require("./size-limit"));
+const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
+const Spinner = React.createFactory(require("./spinner"));
+const Json = require("../utils/json");
+const NetUtils = require("../utils/net");
+
+// Shortcuts
+const DOM = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This template represents 'Response' tab displayed when the user
+ * expands network log in the Console panel. It's responsible for
+ * rendering HTTP response body.
+ *
+ * In case of supported response mime-type (e.g. application/json,
+ * text/xml, etc.), the response is parsed using appropriate parser
+ * and rendered accordingly.
+ */
+var ResponseTab = React.createClass({
+ propTypes: {
+ data: PropTypes.shape({
+ request: PropTypes.object.isRequired,
+ response: PropTypes.object.isRequired
+ }),
+ actions: PropTypes.object.isRequired
+ },
+
+ displayName: "ResponseTab",
+
+ // Response Types
+
+ isJson(content) {
+ if (isLongString(content.text)) {
+ return false;
+ }
+
+ return Json.isJSON(content.mimeType, content.text);
+ },
+
+ parseJson(file) {
+ let content = file.response.content;
+ if (isLongString(content.text)) {
+ return null;
+ }
+
+ let jsonString = new String(content.text);
+ return Json.parseJSONString(jsonString);
+ },
+
+ isImage(content) {
+ if (isLongString(content.text)) {
+ return false;
+ }
+
+ return NetUtils.isImage(content.mimeType);
+ },
+
+ isXml(content) {
+ if (isLongString(content.text)) {
+ return false;
+ }
+
+ return NetUtils.isHTML(content.mimeType);
+ },
+
+ parseXml(file) {
+ let content = file.response.content;
+ if (isLongString(content.text)) {
+ return null;
+ }
+
+ return NetUtils.parseXml(content);
+ },
+
+ // Rendering
+
+ renderJson(file) {
+ let content = file.response.content;
+ if (!this.isJson(content)) {
+ return null;
+ }
+
+ let json = this.parseJson(file);
+ if (!json) {
+ return null;
+ }
+
+ return {
+ key: "json",
+ content: TreeView({
+ columns: [{id: "value"}],
+ object: json,
+ mode: "tiny",
+ renderValue: props => Rep(Object.assign({}, props, {
+ cropLimit: 50,
+ })),
+ }),
+ name: Locale.$STR("jsonScopeName")
+ };
+ },
+
+ renderImage(file) {
+ let content = file.response.content;
+ if (!this.isImage(content)) {
+ return null;
+ }
+
+ let dataUri = "data:" + content.mimeType + ";base64," + content.text;
+ return {
+ key: "image",
+ content: DOM.img({src: dataUri}),
+ name: Locale.$STR("netRequest.image")
+ };
+ },
+
+ renderXml(file) {
+ let content = file.response.content;
+ if (!this.isXml(content)) {
+ return null;
+ }
+
+ let doc = this.parseXml(file);
+ if (!doc) {
+ return null;
+ }
+
+ // Proper component for rendering XML should be used (see bug 1247392)
+ return null;
+ },
+
+ /**
+ * If full response text is available, let's try to parse and
+ * present nicely according to the underlying format.
+ */
+ renderFormattedResponse(file) {
+ let content = file.response.content;
+ if (typeof content.text == "object") {
+ return null;
+ }
+
+ let group = this.renderJson(file);
+ if (group) {
+ return group;
+ }
+
+ group = this.renderImage(file);
+ if (group) {
+ return group;
+ }
+
+ group = this.renderXml(file);
+ if (group) {
+ return group;
+ }
+ },
+
+ renderRawResponse(file) {
+ let group;
+ let content = file.response.content;
+
+ // The response might reached the limit, so check if we are
+ // dealing with a long string.
+ if (typeof content.text == "object") {
+ group = {
+ key: "raw-longstring",
+ name: Locale.$STR("netRequest.rawData"),
+ content: DOM.div({className: "netInfoResponseContent"},
+ content.text.initial,
+ SizeLimit({
+ actions: this.props.actions,
+ data: content,
+ message: Locale.$STR("netRequest.sizeLimitMessage"),
+ link: Locale.$STR("netRequest.sizeLimitMessageLink")
+ })
+ )
+ };
+ } else {
+ group = {
+ key: "raw",
+ name: Locale.$STR("netRequest.rawData"),
+ content: DOM.div({className: "netInfoResponseContent"},
+ content.text
+ )
+ };
+ }
+
+ return group;
+ },
+
+ componentDidMount() {
+ let { actions, data: file } = this.props;
+ let content = file.response.content;
+
+ if (!content || typeof (content.text) == "undefined") {
+ // TODO: use async action objects as soon as Redux is in place
+ actions.requestData("responseContent");
+ }
+ },
+
+ /**
+ * The response panel displays two groups:
+ *
+ * 1) Formatted response (in case of supported format, e.g. JSON, XML, etc.)
+ * 2) Raw response data (always displayed if not discarded)
+ */
+ render() {
+ let { actions, data: file } = this.props;
+
+ // If response bodies are discarded (not collected) let's just
+ // display a info message indicating what to do to collect even
+ // response bodies.
+ if (file.discardResponseBody) {
+ return DOM.span({className: "netInfoBodiesDiscarded"},
+ Locale.$STR("netRequest.responseBodyDiscarded")
+ );
+ }
+
+ // Request for the response content is done only if the response
+ // is not fetched yet - i.e. the `content.text` is undefined.
+ // Empty content.text` can also be a valid response either
+ // empty or not available yet.
+ let content = file.response.content;
+ if (!content || typeof (content.text) == "undefined") {
+ return (
+ Spinner()
+ );
+ }
+
+ // Render response body data. The right representation of the data
+ // is picked according to the content type.
+ let groups = [];
+ groups.push(this.renderFormattedResponse(file));
+ groups.push(this.renderRawResponse(file));
+
+ // Filter out empty groups.
+ groups = groups.filter(group => group);
+
+ // The raw response is collapsed by default if a nice formatted
+ // version is available.
+ if (groups.length > 1) {
+ groups[1].open = false;
+ }
+
+ return (
+ DOM.div({className: "responseTabBox"},
+ DOM.div({className: "panelContent"},
+ NetInfoGroupList({
+ groups: groups
+ })
+ )
+ )
+ );
+ }
+});
+
+// Helpers
+
+function isLongString(text) {
+ return typeof text == "object";
+}
+
+// Exports from this module
+module.exports = ResponseTab;
diff --git a/devtools/client/webconsole/net/components/size-limit.css b/devtools/client/webconsole/net/components/size-limit.css
new file mode 100644
index 000000000..a5c214d9e
--- /dev/null
+++ b/devtools/client/webconsole/net/components/size-limit.css
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/******************************************************************************/
+/* Response Size Limit */
+
+.netInfoBody .netInfoSizeLimit {
+ font-weight: bold;
+ padding-top: 10px;
+}
+
+.netInfoBody .netInfoSizeLimit .objectLink {
+ color: var(--theme-highlight-blue);
+}
diff --git a/devtools/client/webconsole/net/components/size-limit.js b/devtools/client/webconsole/net/components/size-limit.js
new file mode 100644
index 000000000..de8839314
--- /dev/null
+++ b/devtools/client/webconsole/net/components/size-limit.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+
+// Shortcuts
+const DOM = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This template represents a size limit notification message
+ * used e.g. in the Response tab when response body exceeds
+ * size limit. The message contains a link allowing the user
+ * to fetch the rest of the data from the backend (debugger server).
+ */
+var SizeLimit = React.createClass({
+ propTypes: {
+ data: PropTypes.object.isRequired,
+ message: PropTypes.string.isRequired,
+ link: PropTypes.string.isRequired,
+ actions: PropTypes.shape({
+ resolveString: PropTypes.func.isRequired
+ }),
+ },
+
+ displayName: "SizeLimit",
+
+ // Event Handlers
+
+ onClickLimit(event) {
+ let actions = this.props.actions;
+ let content = this.props.data;
+
+ actions.resolveString(content, "text");
+ },
+
+ // Rendering
+
+ render() {
+ let message = this.props.message;
+ let link = this.props.link;
+ let reLink = /^(.*)\{\{link\}\}(.*$)/;
+ let m = message.match(reLink);
+
+ return (
+ DOM.div({className: "netInfoSizeLimit"},
+ DOM.span({}, m[1]),
+ DOM.a({
+ className: "objectLink",
+ onClick: this.onClickLimit},
+ link
+ ),
+ DOM.span({}, m[2])
+ )
+ );
+ }
+});
+
+// Exports from this module
+module.exports = SizeLimit;
diff --git a/devtools/client/webconsole/net/components/spinner.js b/devtools/client/webconsole/net/components/spinner.js
new file mode 100644
index 000000000..fe79f7dd1
--- /dev/null
+++ b/devtools/client/webconsole/net/components/spinner.js
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+
+// Shortcuts
+const DOM = React.DOM;
+
+/**
+ * This template represents a throbber displayed when the UI
+ * is waiting for data coming from the backend (debugging server).
+ */
+var Spinner = React.createClass({
+ displayName: "Spinner",
+
+ render() {
+ return (
+ DOM.div({className: "devtools-throbber"})
+ );
+ }
+});
+
+// Exports from this module
+module.exports = Spinner;
diff --git a/devtools/client/webconsole/net/components/stacktrace-tab.js b/devtools/client/webconsole/net/components/stacktrace-tab.js
new file mode 100644
index 000000000..51eb7689b
--- /dev/null
+++ b/devtools/client/webconsole/net/components/stacktrace-tab.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { PropTypes, createClass, createFactory } = require("devtools/client/shared/vendor/react");
+const StackTrace = createFactory(require("devtools/client/shared/components/stack-trace"));
+
+const StackTraceTab = createClass({
+ displayName: "StackTraceTab",
+
+ propTypes: {
+ data: PropTypes.object.isRequired,
+ actions: PropTypes.shape({
+ onViewSourceInDebugger: PropTypes.func.isRequired
+ })
+ },
+
+ render() {
+ let { stacktrace } = this.props.data.cause;
+ let { actions } = this.props;
+ let onViewSourceInDebugger = actions.onViewSourceInDebugger.bind(actions);
+
+ return StackTrace({ stacktrace, onViewSourceInDebugger });
+ }
+});
+
+// Exports from this module
+module.exports = StackTraceTab;