mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 23:38:38 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
20
devtools/client/webconsole/net/.eslintrc.js
Normal file
20
devtools/client/webconsole/net/.eslintrc.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"globals": {
|
||||
"Locale": true,
|
||||
"Document": true,
|
||||
"document": true,
|
||||
"Node": true,
|
||||
"Element": true,
|
||||
"MessageEvent": true,
|
||||
"BrowserLoader": true,
|
||||
"addEventListener": true,
|
||||
"DOMParser": true,
|
||||
"dispatchEvent": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": ["error", {"args": "none"}],
|
||||
}
|
||||
};
|
||||
75
devtools/client/webconsole/net/components/cookies-tab.js
Normal file
75
devtools/client/webconsole/net/components/cookies-tab.js
Normal file
|
|
@ -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;
|
||||
79
devtools/client/webconsole/net/components/headers-tab.js
Normal file
79
devtools/client/webconsole/net/components/headers-tab.js
Normal file
|
|
@ -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;
|
||||
25
devtools/client/webconsole/net/components/moz.build
Normal file
25
devtools/client/webconsole/net/components/moz.build
Normal file
|
|
@ -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',
|
||||
)
|
||||
112
devtools/client/webconsole/net/components/net-info-body.css
Normal file
112
devtools/client/webconsole/net/components/net-info-body.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
179
devtools/client/webconsole/net/components/net-info-body.js
Normal file
179
devtools/client/webconsole/net/components/net-info-body.js
Normal file
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
80
devtools/client/webconsole/net/components/net-info-group.css
Normal file
80
devtools/client/webconsole/net/components/net-info-group.css
Normal file
|
|
@ -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");
|
||||
}
|
||||
80
devtools/client/webconsole/net/components/net-info-group.js
Normal file
80
devtools/client/webconsole/net/components/net-info-group.js
Normal file
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
58
devtools/client/webconsole/net/components/net-info-params.js
Normal file
58
devtools/client/webconsole/net/components/net-info-params.js
Normal file
|
|
@ -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;
|
||||
41
devtools/client/webconsole/net/components/params-tab.js
Normal file
41
devtools/client/webconsole/net/components/params-tab.js
Normal file
|
|
@ -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;
|
||||
279
devtools/client/webconsole/net/components/post-tab.js
Normal file
279
devtools/client/webconsole/net/components/post-tab.js
Normal file
|
|
@ -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;
|
||||
21
devtools/client/webconsole/net/components/response-tab.css
Normal file
21
devtools/client/webconsole/net/components/response-tab.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
277
devtools/client/webconsole/net/components/response-tab.js
Normal file
277
devtools/client/webconsole/net/components/response-tab.js
Normal file
|
|
@ -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;
|
||||
15
devtools/client/webconsole/net/components/size-limit.css
Normal file
15
devtools/client/webconsole/net/components/size-limit.css
Normal file
|
|
@ -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);
|
||||
}
|
||||
62
devtools/client/webconsole/net/components/size-limit.js
Normal file
62
devtools/client/webconsole/net/components/size-limit.js
Normal file
|
|
@ -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;
|
||||
26
devtools/client/webconsole/net/components/spinner.js
Normal file
26
devtools/client/webconsole/net/components/spinner.js
Normal file
|
|
@ -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;
|
||||
29
devtools/client/webconsole/net/components/stacktrace-tab.js
Normal file
29
devtools/client/webconsole/net/components/stacktrace-tab.js
Normal file
|
|
@ -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;
|
||||
66
devtools/client/webconsole/net/data-provider.js
Normal file
66
devtools/client/webconsole/net/data-provider.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/* 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 promise = require("promise");
|
||||
|
||||
/**
|
||||
* Map of pending requests. Used mainly by tests to wait
|
||||
* till things are ready.
|
||||
*/
|
||||
var promises = new Map();
|
||||
|
||||
/**
|
||||
* This object is used to fetch network data from the backend.
|
||||
* Communication with the chrome scope is based on message
|
||||
* exchange.
|
||||
*/
|
||||
var DataProvider = {
|
||||
hasPendingRequests: function () {
|
||||
return promises.size > 0;
|
||||
},
|
||||
|
||||
requestData: function (client, actor, method) {
|
||||
let key = actor + ":" + method;
|
||||
let p = promises.get(key);
|
||||
if (p) {
|
||||
return p;
|
||||
}
|
||||
|
||||
let deferred = promise.defer();
|
||||
let realMethodName = "get" + method.charAt(0).toUpperCase() +
|
||||
method.slice(1);
|
||||
|
||||
if (!client[realMethodName]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
client[realMethodName](actor, response => {
|
||||
promises.delete(key);
|
||||
deferred.resolve(response);
|
||||
});
|
||||
|
||||
promises.set(key, deferred.promise);
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
resolveString: function (client, stringGrip) {
|
||||
let key = stringGrip.actor + ":getString";
|
||||
let p = promises.get(key);
|
||||
if (p) {
|
||||
return p;
|
||||
}
|
||||
|
||||
p = client.getString(stringGrip).then(result => {
|
||||
promises.delete(key);
|
||||
return result;
|
||||
});
|
||||
|
||||
promises.set(key, p);
|
||||
return p;
|
||||
},
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
module.exports = DataProvider;
|
||||
98
devtools/client/webconsole/net/main.js
Normal file
98
devtools/client/webconsole/net/main.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/* 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";
|
||||
|
||||
/* global BrowserLoader */
|
||||
|
||||
var { utils: Cu } = Components;
|
||||
|
||||
// Initialize module loader and load all modules of the new inline
|
||||
// preview feature. The entire code-base doesn't need any extra
|
||||
// privileges and runs entirely in content scope.
|
||||
const rootUrl = "resource://devtools/client/webconsole/net/";
|
||||
const require = BrowserLoader({
|
||||
baseURI: rootUrl,
|
||||
window}).require;
|
||||
|
||||
const NetRequest = require("./net-request");
|
||||
const { loadSheet } = require("sdk/stylesheet/utils");
|
||||
|
||||
// Localization
|
||||
const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
const L10N = new LocalizationHelper("devtools/client/locales/netmonitor.properties");
|
||||
|
||||
// Stylesheets
|
||||
var styleSheets = [
|
||||
"resource://devtools/client/jsonview/css/toolbar.css",
|
||||
"resource://devtools/client/shared/components/tree/tree-view.css",
|
||||
"resource://devtools/client/shared/components/reps/reps.css",
|
||||
"resource://devtools/client/webconsole/net/net-request.css",
|
||||
"resource://devtools/client/webconsole/net/components/size-limit.css",
|
||||
"resource://devtools/client/webconsole/net/components/net-info-body.css",
|
||||
"resource://devtools/client/webconsole/net/components/net-info-group.css",
|
||||
"resource://devtools/client/webconsole/net/components/net-info-params.css",
|
||||
"resource://devtools/client/webconsole/net/components/response-tab.css"
|
||||
];
|
||||
|
||||
// Load theme stylesheets into the Console frame. This should be
|
||||
// done automatically by UI Components as soon as we have consensus
|
||||
// on the right CSS strategy FIXME.
|
||||
// It would also be nice to include them using @import.
|
||||
styleSheets.forEach(url => {
|
||||
loadSheet(this, url, "author");
|
||||
});
|
||||
|
||||
// Localization API used by React components
|
||||
// accessing strings from *.properties file.
|
||||
// Example:
|
||||
// let localizedString = Locale.$STR('string-key');
|
||||
//
|
||||
// Resources:
|
||||
// http://l20n.org/
|
||||
// https://github.com/yahoo/react-intl
|
||||
this.Locale = {
|
||||
$STR: key => {
|
||||
try {
|
||||
return L10N.getStr(key);
|
||||
} catch (err) {
|
||||
console.error(key + ": " + err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// List of NetRequest instances represents the state.
|
||||
// As soon as Redux is in place it should be maintained using a reducer.
|
||||
var netRequests = new Map();
|
||||
|
||||
/**
|
||||
* This function handles network events received from the backend. It's
|
||||
* executed from within the webconsole.js
|
||||
*/
|
||||
function onNetworkEvent(log) {
|
||||
// The 'from' field is set only in case of a 'networkEventUpdate' packet.
|
||||
// The initial 'networkEvent' packet uses 'actor'.
|
||||
// Check if NetRequest object is already created for this event actor and
|
||||
// if there is none make sure to create one.
|
||||
let response = log.response;
|
||||
let netRequest = response.from ? netRequests.get(response.from) : null;
|
||||
if (!netRequest && !log.update) {
|
||||
netRequest = new NetRequest(log);
|
||||
netRequests.set(response.actor, netRequest);
|
||||
}
|
||||
|
||||
if (!netRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.update) {
|
||||
netRequest.updateBody(response);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make the 'onNetworkEvent' accessible from chrome (see webconsole.js)
|
||||
this.NetRequest = {
|
||||
onNetworkEvent: onNetworkEvent
|
||||
};
|
||||
19
devtools/client/webconsole/net/moz.build
Normal file
19
devtools/client/webconsole/net/moz.build
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'components',
|
||||
'utils'
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'data-provider.js',
|
||||
'main.js',
|
||||
'net-request.css',
|
||||
'net-request.js',
|
||||
)
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
BROWSER_CHROME_MANIFESTS += ['test/mochitest/browser.ini']
|
||||
35
devtools/client/webconsole/net/net-request.css
Normal file
35
devtools/client/webconsole/net/net-request.css
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* 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/. */
|
||||
|
||||
/******************************************************************************/
|
||||
/* General */
|
||||
|
||||
:root {
|
||||
--net-border: #d7d7d7;
|
||||
}
|
||||
|
||||
:root.theme-dark {
|
||||
--net-border: #5f7387;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Network log */
|
||||
|
||||
/* No background if a Net log is opened */
|
||||
.netRequest.message.opened,
|
||||
.netRequest.message.opened:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Themes */
|
||||
|
||||
.theme-dark .netRequest.opened:hover,
|
||||
.theme-dark .netRequest.opened {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.theme-firebug .netRequest.message.opened:hover {
|
||||
background-image: linear-gradient(rgba(214, 233, 246, 0.8), rgba(255, 255, 255, 1.6)) !important;
|
||||
}
|
||||
323
devtools/client/webconsole/net/net-request.js
Normal file
323
devtools/client/webconsole/net/net-request.js
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
/* 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";
|
||||
|
||||
// React
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
// Reps
|
||||
const { parseURLParams } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
|
||||
// Network
|
||||
const { cancelEvent, isLeftClick } = require("./utils/events");
|
||||
const NetInfoBody = React.createFactory(require("./components/net-info-body"));
|
||||
const DataProvider = require("./data-provider");
|
||||
|
||||
// Constants
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
/**
|
||||
* This object represents a network log in the Console panel (and in the
|
||||
* Network panel in the future).
|
||||
* It's associated with an existing log and so, also with an existing
|
||||
* element in the DOM.
|
||||
*
|
||||
* The object neither render no request for more data by default. It only
|
||||
* reqisters a click listener to the associated log entry (a network event)
|
||||
* and changes the class attribute of the log entry, so a twisty icon
|
||||
* appears to indicates that there are more details displayed if the
|
||||
* log entry is expanded.
|
||||
*
|
||||
* When the user expands the log, data are requested from the backend
|
||||
* and rendered directly within the Console iframe.
|
||||
*/
|
||||
function NetRequest(log) {
|
||||
this.initialize(log);
|
||||
}
|
||||
|
||||
NetRequest.prototype = {
|
||||
initialize: function (log) {
|
||||
this.client = log.consoleFrame.webConsoleClient;
|
||||
this.owner = log.consoleFrame.owner;
|
||||
|
||||
// 'this.file' field is following HAR spec.
|
||||
// http://www.softwareishard.com/blog/har-12-spec/
|
||||
this.file = log.response;
|
||||
this.parentNode = log.node;
|
||||
this.file.request.queryString = parseURLParams(this.file.request.url);
|
||||
this.hasCookies = false;
|
||||
|
||||
// Map of fetched responses (to avoid unnecessary RDP round trip).
|
||||
this.cachedResponses = new Map();
|
||||
|
||||
let doc = this.parentNode.ownerDocument;
|
||||
let twisty = doc.createElementNS(XHTML_NS, "a");
|
||||
twisty.className = "theme-twisty";
|
||||
twisty.href = "#";
|
||||
|
||||
let messageBody = this.parentNode.querySelector(".message-body-wrapper");
|
||||
this.parentNode.insertBefore(twisty, messageBody);
|
||||
this.parentNode.setAttribute("collapsible", true);
|
||||
|
||||
this.parentNode.classList.add("netRequest");
|
||||
|
||||
// Register a click listener.
|
||||
this.addClickListener();
|
||||
},
|
||||
|
||||
addClickListener: function () {
|
||||
// Add an event listener to toggle the expanded state when clicked.
|
||||
// The event bubbling is canceled if the user clicks on the log
|
||||
// itself (not on the expanded body), so opening of the default
|
||||
// modal dialog is avoided.
|
||||
this.parentNode.addEventListener("click", (event) => {
|
||||
if (!isLeftClick(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clicking on the toggle button or the method expands/collapses
|
||||
// the body with HTTP details.
|
||||
let classList = event.originalTarget.classList;
|
||||
if (!(classList.contains("theme-twisty") ||
|
||||
classList.contains("method"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Alright, the user is clicking fine, let's open HTTP details!
|
||||
this.onToggleBody(event);
|
||||
|
||||
// Avoid the default modal dialog
|
||||
cancelEvent(event);
|
||||
}, true);
|
||||
},
|
||||
|
||||
onToggleBody: function (event) {
|
||||
let target = event.currentTarget;
|
||||
let logRow = target.closest(".netRequest");
|
||||
logRow.classList.toggle("opened");
|
||||
|
||||
let twisty = this.parentNode.querySelector(".theme-twisty");
|
||||
if (logRow.classList.contains("opened")) {
|
||||
twisty.setAttribute("open", true);
|
||||
} else {
|
||||
twisty.removeAttribute("open");
|
||||
}
|
||||
|
||||
let isOpen = logRow.classList.contains("opened");
|
||||
if (isOpen) {
|
||||
this.renderBody();
|
||||
} else {
|
||||
this.closeBody();
|
||||
}
|
||||
},
|
||||
|
||||
updateCookies: function(method, response) {
|
||||
// TODO: This code will be part of a reducer.
|
||||
let result;
|
||||
if (response.cookies > 0 &&
|
||||
["requestCookies", "responseCookies"].includes(method)) {
|
||||
this.hasCookies = true;
|
||||
this.refresh();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Executed when 'networkEventUpdate' is received from the backend.
|
||||
*/
|
||||
updateBody: function (response) {
|
||||
// 'networkEventUpdate' event indicates that there are new data
|
||||
// available on the backend. The following logic checks the response
|
||||
// cache and if this data has been already requested before they
|
||||
// need to be updated now (re-requested).
|
||||
let method = response.updateType;
|
||||
this.updateCookies(method, response);
|
||||
if (this.cachedResponses.get(method)) {
|
||||
this.cachedResponses.delete(method);
|
||||
this.requestData(method);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Close network inline preview body.
|
||||
*/
|
||||
closeBody: function () {
|
||||
this.netInfoBodyBox.parentNode.removeChild(this.netInfoBodyBox);
|
||||
},
|
||||
|
||||
/**
|
||||
* Render network inline preview body.
|
||||
*/
|
||||
renderBody: function () {
|
||||
let messageBody = this.parentNode.querySelector(".message-body-wrapper");
|
||||
|
||||
// Create box for all markup rendered by ReactJS. Since we are
|
||||
// rendering within webconsole.xul (i.e. XUL document) we need
|
||||
// to explicitly specify XHTML namespace.
|
||||
let doc = messageBody.ownerDocument;
|
||||
this.netInfoBodyBox = doc.createElementNS(XHTML_NS, "div");
|
||||
this.netInfoBodyBox.classList.add("netInfoBody");
|
||||
messageBody.appendChild(this.netInfoBodyBox);
|
||||
|
||||
// As soon as Redux is in place state and actions will come from
|
||||
// separate modules.
|
||||
let body = NetInfoBody({
|
||||
actions: this
|
||||
});
|
||||
|
||||
// Render net info body!
|
||||
this.body = ReactDOM.render(body, this.netInfoBodyBox);
|
||||
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
/**
|
||||
* Render top level ReactJS component.
|
||||
*/
|
||||
refresh: function () {
|
||||
if (!this.netInfoBodyBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: As soon as Redux is in place there will be reducer
|
||||
// computing a new state.
|
||||
let newState = Object.assign({}, this.body.state, {
|
||||
data: this.file,
|
||||
hasCookies: this.hasCookies
|
||||
});
|
||||
|
||||
this.body.setState(newState);
|
||||
},
|
||||
|
||||
// Communication with the backend
|
||||
|
||||
requestData: function (method) {
|
||||
// If the response has already been received bail out.
|
||||
let response = this.cachedResponses.get(method);
|
||||
if (response) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set an attribute indicating that this net log is waiting for
|
||||
// data coming from the backend. Intended mainly for tests.
|
||||
this.parentNode.setAttribute("loading", "true");
|
||||
|
||||
let actor = this.file.actor;
|
||||
DataProvider.requestData(this.client, actor, method).then(args => {
|
||||
this.cachedResponses.set(method, args);
|
||||
this.onRequestData(method, args);
|
||||
|
||||
if (!DataProvider.hasPendingRequests()) {
|
||||
this.parentNode.removeAttribute("loading");
|
||||
|
||||
// Fire an event indicating that all pending requests for
|
||||
// data from the backend has finished. Intended for tests.
|
||||
// Do it asynchronously so, it's done after all handlers
|
||||
// for the current promise are executed.
|
||||
setTimeout(() => {
|
||||
let event = document.createEvent("Event");
|
||||
event.initEvent("netlog-no-pending-requests", true, true);
|
||||
this.parentNode.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onRequestData: function (method, response) {
|
||||
// TODO: This code will be part of a reducer.
|
||||
let result;
|
||||
switch (method) {
|
||||
case "requestHeaders":
|
||||
result = this.onRequestHeaders(response);
|
||||
break;
|
||||
case "responseHeaders":
|
||||
result = this.onResponseHeaders(response);
|
||||
break;
|
||||
case "requestCookies":
|
||||
result = this.onRequestCookies(response);
|
||||
break;
|
||||
case "responseCookies":
|
||||
result = this.onResponseCookies(response);
|
||||
break;
|
||||
case "responseContent":
|
||||
result = this.onResponseContent(response);
|
||||
break;
|
||||
case "requestPostData":
|
||||
result = this.onRequestPostData(response);
|
||||
break;
|
||||
}
|
||||
|
||||
result.then(() => {
|
||||
this.refresh();
|
||||
});
|
||||
},
|
||||
|
||||
onRequestHeaders: function (response) {
|
||||
this.file.request.headers = response.headers;
|
||||
|
||||
return this.resolveHeaders(this.file.request.headers);
|
||||
},
|
||||
|
||||
onResponseHeaders: function (response) {
|
||||
this.file.response.headers = response.headers;
|
||||
|
||||
return this.resolveHeaders(this.file.response.headers);
|
||||
},
|
||||
|
||||
onResponseContent: function (response) {
|
||||
let content = response.content;
|
||||
|
||||
for (let p in content) {
|
||||
this.file.response.content[p] = content[p];
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
onRequestPostData: function (response) {
|
||||
this.file.request.postData = response.postData;
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
onRequestCookies: function (response) {
|
||||
this.file.request.cookies = response.cookies;
|
||||
return this.resolveHeaders(this.file.request.cookies);
|
||||
},
|
||||
|
||||
onResponseCookies: function (response) {
|
||||
this.file.response.cookies = response.cookies;
|
||||
return this.resolveHeaders(this.file.response.cookies);
|
||||
},
|
||||
|
||||
onViewSourceInDebugger: function (frame) {
|
||||
this.owner.viewSourceInDebugger(frame.source, frame.line);
|
||||
},
|
||||
|
||||
resolveHeaders: function (headers) {
|
||||
let promises = [];
|
||||
|
||||
for (let header of headers) {
|
||||
if (typeof header.value == "object") {
|
||||
promises.push(this.resolveString(header.value).then(value => {
|
||||
header.value = value;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
},
|
||||
|
||||
resolveString: function (object, propName) {
|
||||
let stringGrip = object[propName];
|
||||
if (typeof stringGrip == "object") {
|
||||
DataProvider.resolveString(this.client, stringGrip).then(args => {
|
||||
object[propName] = args;
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
module.exports = NetRequest;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../../../.eslintrc.mochitests.js",
|
||||
};
|
||||
22
devtools/client/webconsole/net/test/mochitest/browser.ini
Normal file
22
devtools/client/webconsole/net/test/mochitest/browser.ini
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
head.js
|
||||
page_basic.html
|
||||
test.json
|
||||
test.json^headers^
|
||||
test-cookies.json
|
||||
test-cookies.json^headers^
|
||||
test.txt
|
||||
test.xml
|
||||
test.xml^headers^
|
||||
!/devtools/client/webconsole/test/head.js
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
|
||||
[browser_net_basic.js]
|
||||
[browser_net_cookies.js]
|
||||
[browser_net_headers.js]
|
||||
[browser_net_params.js]
|
||||
[browser_net_post.js]
|
||||
[browser_net_response.js]
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
|
||||
const JSON_XHR_URL = URL_ROOT + "test.json";
|
||||
|
||||
/**
|
||||
* Basic test that generates XHR in the content and
|
||||
* checks the related log in the Console panel can
|
||||
* be expanded.
|
||||
*/
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy basic started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "GET",
|
||||
url: JSON_XHR_URL
|
||||
});
|
||||
|
||||
ok(netInfoBody, "The network details must be available");
|
||||
|
||||
// There should be at least two tabs: Headers and Response
|
||||
ok(netInfoBody.querySelector(".tabs .tabs-menu-item.headers"),
|
||||
"Headers tab must be available");
|
||||
ok(netInfoBody.querySelector(".tabs .tabs-menu-item.response"),
|
||||
"Response tab must be available");
|
||||
});
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
|
||||
const JSON_XHR_URL = URL_ROOT + "test-cookies.json";
|
||||
|
||||
/**
|
||||
* This test generates XHR requests in the page, expands
|
||||
* networks details in the Console panel and checks that
|
||||
* Cookies are properly displayed.
|
||||
*/
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy cookies started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "GET",
|
||||
url: JSON_XHR_URL
|
||||
});
|
||||
|
||||
// Select "Cookies" tab
|
||||
let tabBody = yield selectNetInfoTab(hud, netInfoBody, "cookies");
|
||||
|
||||
let requestCookieName = tabBody.querySelector(
|
||||
".netInfoGroup.requestCookies .netInfoParamName > span[title='bar']");
|
||||
|
||||
// Verify request cookies (name and value)
|
||||
ok(requestCookieName, "Request Cookie name must exist");
|
||||
is(requestCookieName.textContent, "bar",
|
||||
"The cookie name must have proper value");
|
||||
|
||||
let requestCookieValue = requestCookieName.parentNode.nextSibling;
|
||||
ok(requestCookieValue, "Request Cookie value must exist");
|
||||
is(requestCookieValue.textContent, "foo",
|
||||
"The cookie value must have proper value");
|
||||
|
||||
let responseCookieName = tabBody.querySelector(
|
||||
".netInfoGroup.responseCookies .netInfoParamName > span[title='test']");
|
||||
|
||||
// Verify response cookies (name and value)
|
||||
ok(responseCookieName, "Response Cookie name must exist");
|
||||
is(responseCookieName.textContent, "test",
|
||||
"The cookie name must have proper value");
|
||||
|
||||
let responseCookieValue = responseCookieName.parentNode.nextSibling;
|
||||
ok(responseCookieValue, "Response Cookie value must exist");
|
||||
is(responseCookieValue.textContent, "abc",
|
||||
"The cookie value must have proper value");
|
||||
});
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
|
||||
const JSON_XHR_URL = URL_ROOT + "test.json";
|
||||
|
||||
/**
|
||||
* This test generates XHR requests in the page, expands
|
||||
* networks details in the Console panel and checks that
|
||||
* HTTP headers are there.
|
||||
*/
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy headers started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "GET",
|
||||
url: JSON_XHR_URL
|
||||
});
|
||||
|
||||
// Select "Headers" tab
|
||||
let tabBody = yield selectNetInfoTab(hud, netInfoBody, "headers");
|
||||
let paramName = tabBody.querySelector(
|
||||
".netInfoParamName > span[title='Content-Type']");
|
||||
|
||||
// Verify "Content-Type" header (name and value)
|
||||
ok(paramName, "Header name must exist");
|
||||
is(paramName.textContent, "Content-Type",
|
||||
"The header name must have proper value");
|
||||
|
||||
let paramValue = paramName.parentNode.nextSibling;
|
||||
ok(paramValue, "Header value must exist");
|
||||
is(paramValue.textContent, "application/json; charset=utf-8",
|
||||
"The header value must have proper value");
|
||||
});
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
|
||||
const JSON_XHR_URL = URL_ROOT + "test.json";
|
||||
|
||||
/**
|
||||
* This test generates XHR requests in the page, expands
|
||||
* networks details in the Console panel and checks that
|
||||
* HTTP parameters (query string) are there.
|
||||
*/
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy params started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "GET",
|
||||
url: JSON_XHR_URL,
|
||||
queryString: "?foo=bar"
|
||||
});
|
||||
|
||||
// Check headers
|
||||
let tabBody = yield selectNetInfoTab(hud, netInfoBody, "params");
|
||||
|
||||
let paramName = tabBody.querySelector(
|
||||
".netInfoParamName > span[title='foo']");
|
||||
|
||||
// Verify "Content-Type" header (name and value)
|
||||
ok(paramName, "Header name must exist");
|
||||
is(paramName.textContent, "foo",
|
||||
"The param name must have proper value");
|
||||
|
||||
let paramValue = paramName.parentNode.nextSibling;
|
||||
ok(paramValue, "param value must exist");
|
||||
is(paramValue.textContent, "bar",
|
||||
"The param value must have proper value");
|
||||
});
|
||||
|
||||
/**
|
||||
* Test URL parameters with the same name.
|
||||
*/
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy params started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "GET",
|
||||
url: JSON_XHR_URL,
|
||||
queryString: "?box[]=123&box[]=456"
|
||||
});
|
||||
|
||||
// Check headers
|
||||
let tabBody = yield selectNetInfoTab(hud, netInfoBody, "params");
|
||||
|
||||
let params = tabBody.querySelectorAll(
|
||||
".netInfoParamName > span[title='box[]']");
|
||||
is(params.length, 2, "Two URI parameters must exist");
|
||||
|
||||
let values = tabBody.querySelectorAll(
|
||||
".netInfoParamValue > code");
|
||||
is(values[0].textContent, 123, "First value must match");
|
||||
is(values[1].textContent, 456, "Second value must match");
|
||||
});
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
|
||||
const JSON_XHR_URL = URL_ROOT + "test.json";
|
||||
|
||||
const plainPostBody = "test-data";
|
||||
const jsonData = "{\"bar\": \"baz\"}";
|
||||
const jsonRendered = "bar\"baz\"";
|
||||
const xmlPostBody = "<xml><name>John</name></xml>";
|
||||
|
||||
/**
|
||||
* This test generates XHR requests in the page, expands
|
||||
* networks details in the Console panel and checks that
|
||||
* Post data are properly rendered.
|
||||
*/
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy post plain body started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "POST",
|
||||
url: JSON_XHR_URL,
|
||||
body: plainPostBody
|
||||
});
|
||||
|
||||
// Check post body data
|
||||
let tabBody = yield selectNetInfoTab(hud, netInfoBody, "post");
|
||||
let postContent = tabBody.querySelector(
|
||||
".netInfoGroup.raw.opened .netInfoGroupContent");
|
||||
is(postContent.textContent, plainPostBody,
|
||||
"Post body must be properly rendered");
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy post JSON body started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "POST",
|
||||
url: JSON_XHR_URL,
|
||||
body: jsonData,
|
||||
requestHeaders: [{
|
||||
name: "Content-Type",
|
||||
value: "application/json"
|
||||
}]
|
||||
});
|
||||
|
||||
// Check post body data
|
||||
let tabBody = yield selectNetInfoTab(hud, netInfoBody, "post");
|
||||
let postContent = tabBody.querySelector(
|
||||
".netInfoGroup.json.opened .netInfoGroupContent");
|
||||
is(postContent.textContent, jsonRendered,
|
||||
"Post body must be properly rendered");
|
||||
|
||||
let rawPostContent = tabBody.querySelector(
|
||||
".netInfoGroup.raw.opened .netInfoGroupContent");
|
||||
ok(!rawPostContent, "Raw response group must be collapsed");
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy post XML body started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "POST",
|
||||
url: JSON_XHR_URL,
|
||||
body: xmlPostBody,
|
||||
requestHeaders: [{
|
||||
name: "Content-Type",
|
||||
value: "application/xml"
|
||||
}]
|
||||
});
|
||||
|
||||
// Check post body data
|
||||
let tabBody = yield selectNetInfoTab(hud, netInfoBody, "post");
|
||||
let rawPostContent = tabBody.querySelector(
|
||||
".netInfoGroup.raw.opened .netInfoGroupContent");
|
||||
is(rawPostContent.textContent, xmlPostBody,
|
||||
"Raw response group must not be collapsed");
|
||||
});
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
|
||||
const TEXT_XHR_URL = URL_ROOT + "test.txt";
|
||||
const JSON_XHR_URL = URL_ROOT + "test.json";
|
||||
const XML_XHR_URL = URL_ROOT + "test.xml";
|
||||
|
||||
const textResponseBody = "this is a response";
|
||||
const jsonResponseBody = "name\"John\"";
|
||||
|
||||
// Individual tests below generate XHR request in the page, expand
|
||||
// network details in the Console panel and checks various types
|
||||
// of response bodies.
|
||||
|
||||
/**
|
||||
* Validate plain text response
|
||||
*/
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy respone plain body started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "GET",
|
||||
url: TEXT_XHR_URL,
|
||||
});
|
||||
|
||||
// Check response body data
|
||||
let tabBody = yield selectNetInfoTab(hud, netInfoBody, "response");
|
||||
let responseContent = tabBody.querySelector(
|
||||
".netInfoGroup.raw.opened .netInfoGroupContent");
|
||||
|
||||
ok(responseContent.textContent.indexOf(textResponseBody) > -1,
|
||||
"Response body must be properly rendered");
|
||||
});
|
||||
|
||||
/**
|
||||
* Validate XML response
|
||||
*/
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy response XML body started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "GET",
|
||||
url: XML_XHR_URL,
|
||||
});
|
||||
|
||||
// Check response body data
|
||||
let tabBody = yield selectNetInfoTab(hud, netInfoBody, "response");
|
||||
let rawResponseContent = tabBody.querySelector(
|
||||
".netInfoGroup.raw.opened .netInfoGroupContent");
|
||||
ok(rawResponseContent, "Raw response group must not be collapsed");
|
||||
});
|
||||
|
||||
/**
|
||||
* Validate JSON response
|
||||
*/
|
||||
add_task(function* () {
|
||||
info("Test XHR Spy response JSON body started");
|
||||
|
||||
let {hud} = yield addTestTab(TEST_PAGE_URL);
|
||||
|
||||
let netInfoBody = yield executeAndInspectXhr(hud, {
|
||||
method: "GET",
|
||||
url: JSON_XHR_URL,
|
||||
});
|
||||
|
||||
// Check response body data
|
||||
let tabBody = yield selectNetInfoTab(hud, netInfoBody, "response");
|
||||
let responseContent = tabBody.querySelector(
|
||||
".netInfoGroup.json .netInfoGroupContent");
|
||||
|
||||
is(responseContent.textContent, jsonResponseBody,
|
||||
"Response body must be properly rendered");
|
||||
|
||||
let rawResponseContent = tabBody.querySelector(
|
||||
".netInfoGroup.raw.opened .netInfoGroupContent");
|
||||
ok(!rawResponseContent, "Raw response group must be collapsed");
|
||||
});
|
||||
209
devtools/client/webconsole/net/test/mochitest/head.js
Normal file
209
devtools/client/webconsole/net/test/mochitest/head.js
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
|
||||
/* import-globals-from ../../../test/head.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Load Web Console head.js, it implements helper console test API
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/webconsole/test/head.js", this);
|
||||
|
||||
const FRAME_SCRIPT_UTILS_URL =
|
||||
"chrome://devtools/content/shared/frame-script-utils.js";
|
||||
|
||||
const NET_INFO_PREF = "devtools.webconsole.filter.networkinfo";
|
||||
const NET_XHR_PREF = "devtools.webconsole.filter.netxhr";
|
||||
|
||||
// Enable XHR logging for the test
|
||||
Services.prefs.setBoolPref(NET_INFO_PREF, true);
|
||||
Services.prefs.setBoolPref(NET_XHR_PREF, true);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(NET_INFO_PREF, true);
|
||||
Services.prefs.clearUserPref(NET_XHR_PREF, true);
|
||||
});
|
||||
|
||||
// Use the old webconsole since the new one doesn't yet support
|
||||
// XHR spy. See Bug 1304794.
|
||||
Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", false);
|
||||
registerCleanupFunction(function* () {
|
||||
Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
|
||||
});
|
||||
|
||||
/**
|
||||
* Add a new test tab in the browser and load the given url.
|
||||
* @param {String} url The url to be loaded in the new tab
|
||||
* @return a promise that resolves to the tab object when the url is loaded
|
||||
*/
|
||||
function addTestTab(url) {
|
||||
info("Adding a new JSON tab with URL: '" + url + "'");
|
||||
|
||||
return Task.spawn(function* () {
|
||||
let tab = yield addTab(url);
|
||||
|
||||
// Load devtools/shared/frame-script-utils.js
|
||||
loadCommonFrameScript(tab);
|
||||
|
||||
// Open the Console panel
|
||||
let hud = yield openConsole();
|
||||
|
||||
return {
|
||||
tab: tab,
|
||||
browser: tab.linkedBrowser,
|
||||
hud: hud
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param hud
|
||||
* @param options
|
||||
*/
|
||||
function executeAndInspectXhr(hud, options) {
|
||||
hud.jsterm.clearOutput();
|
||||
|
||||
options.queryString = options.queryString || "";
|
||||
|
||||
// Execute XHR in the content scope.
|
||||
performRequestsInContent({
|
||||
method: options.method,
|
||||
url: options.url + options.queryString,
|
||||
body: options.body,
|
||||
nocache: options.nocache,
|
||||
requestHeaders: options.requestHeaders
|
||||
});
|
||||
|
||||
return Task.spawn(function* () {
|
||||
// Wait till the appropriate Net log appears in the Console panel.
|
||||
let rules = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: options.url,
|
||||
category: CATEGORY_NETWORK,
|
||||
severity: SEVERITY_INFO,
|
||||
isXhr: true,
|
||||
}]
|
||||
});
|
||||
|
||||
// The log is here, get its parent element (className: 'message').
|
||||
let msg = [...rules[0].matched][0];
|
||||
let body = msg.querySelector(".message-body");
|
||||
|
||||
// Open XHR HTTP details body and wait till the UI fetches
|
||||
// all necessary data from the backend. All RPD requests
|
||||
// needs to be finished before we can continue testing.
|
||||
yield synthesizeMouseClickSoon(hud, body);
|
||||
yield waitForBackend(msg);
|
||||
let netInfoBody = body.querySelector(".netInfoBody");
|
||||
ok(netInfoBody, "Net info body must exist");
|
||||
return netInfoBody;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait till XHR data are fetched from the backend (i.e. there are
|
||||
* no pending RDP requests.
|
||||
*/
|
||||
function waitForBackend(element) {
|
||||
if (!element.hasAttribute("loading")) {
|
||||
return;
|
||||
}
|
||||
return once(element, "netlog-no-pending-requests", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select specific tab in XHR info body.
|
||||
*
|
||||
* @param netInfoBody The main XHR info body
|
||||
* @param tabId Tab ID (possible values: 'headers', 'cookies', 'params',
|
||||
* 'post', 'response');
|
||||
*
|
||||
* @returns Tab body element.
|
||||
*/
|
||||
function selectNetInfoTab(hud, netInfoBody, tabId) {
|
||||
let tab = netInfoBody.querySelector(".tabs-menu-item." + tabId);
|
||||
ok(tab, "Tab must exist " + tabId);
|
||||
|
||||
// Click to select specified tab and wait till its
|
||||
// UI is populated with data from the backend.
|
||||
// There must be no pending RDP requests before we can
|
||||
// continue testing the UI.
|
||||
return Task.spawn(function* () {
|
||||
yield synthesizeMouseClickSoon(hud, tab);
|
||||
let msg = getAncestorByClass(netInfoBody, "message");
|
||||
yield waitForBackend(msg);
|
||||
let tabBody = netInfoBody.querySelector("." + tabId + "TabBox");
|
||||
ok(tabBody, "Tab body must exist");
|
||||
return tabBody;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return parent node with specified class.
|
||||
*
|
||||
* @param node A child element
|
||||
* @param className Specified class name.
|
||||
*
|
||||
* @returns A parent element.
|
||||
*/
|
||||
function getAncestorByClass(node, className) {
|
||||
for (let parent = node; parent; parent = parent.parentNode) {
|
||||
if (parent.classList && parent.classList.contains(className)) {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize asynchronous click event (with clean stack trace).
|
||||
*/
|
||||
function synthesizeMouseClickSoon(hud, element) {
|
||||
return new Promise((resolve) => {
|
||||
executeSoon(() => {
|
||||
EventUtils.synthesizeMouse(element, 2, 2, {}, hud.iframeWindow);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute XHR in the content scope.
|
||||
*/
|
||||
function performRequestsInContent(requests) {
|
||||
info("Performing requests in the context of the content.");
|
||||
return executeInContent("devtools:test:xhr", requests);
|
||||
}
|
||||
|
||||
function executeInContent(name, data = {}, objects = {},
|
||||
expectResponse = true) {
|
||||
let mm = gBrowser.selectedBrowser.messageManager;
|
||||
|
||||
mm.sendAsyncMessage(name, data, objects);
|
||||
if (expectResponse) {
|
||||
return waitForContentMessage(name);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function waitForContentMessage(name) {
|
||||
info("Expecting message " + name + " from content");
|
||||
|
||||
let mm = gBrowser.selectedBrowser.messageManager;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
mm.addMessageListener(name, function onMessage(msg) {
|
||||
mm.removeMessageListener(name, onMessage);
|
||||
resolve(msg.data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadCommonFrameScript(tab) {
|
||||
let browser = tab ? tab.linkedBrowser : gBrowser.selectedBrowser;
|
||||
browser.messageManager.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>XHR Spy test page</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
document.cookie = "bar=foo";
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"name":"Cookies Test"}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Content-Type: application/json; charset=utf-8
|
||||
Set-Cookie: test=abc
|
||||
1
devtools/client/webconsole/net/test/mochitest/test.json
Normal file
1
devtools/client/webconsole/net/test/mochitest/test.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"name":"John"}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Content-Type: application/json; charset=utf-8
|
||||
1
devtools/client/webconsole/net/test/mochitest/test.txt
Normal file
1
devtools/client/webconsole/net/test/mochitest/test.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
this is a response
|
||||
1
devtools/client/webconsole/net/test/mochitest/test.xml
Normal file
1
devtools/client/webconsole/net/test/mochitest/test.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<xml><name>John</name></xml>
|
||||
|
|
@ -0,0 +1 @@
|
|||
Content-Type: application/xml; charset=utf-8
|
||||
6
devtools/client/webconsole/net/test/unit/.eslintrc.js
Normal file
6
devtools/client/webconsole/net/test/unit/.eslintrc.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the common devtools xpcshell eslintrc config.
|
||||
"extends": "../../../../../.eslintrc.xpcshell.js"
|
||||
};
|
||||
45
devtools/client/webconsole/net/test/unit/test_json-utils.js
Normal file
45
devtools/client/webconsole/net/test/unit/test_json-utils.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var Cu = Components.utils;
|
||||
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const { parseJSONString, isJSON } = require("devtools/client/webconsole/net/utils/json");
|
||||
|
||||
// Test data
|
||||
const simpleJson = '{"name":"John"}';
|
||||
const jsonInFunc = 'someFunc({"name":"John"})';
|
||||
|
||||
const json1 = "{'a': 1}";
|
||||
const json2 = " {'a': 1}";
|
||||
const json3 = "\t {'a': 1}";
|
||||
const json4 = "\n\n\t {'a': 1}";
|
||||
const json5 = "\n\n\t ";
|
||||
|
||||
const textMimeType = "text/plain";
|
||||
const jsonMimeType = "text/javascript";
|
||||
const unknownMimeType = "text/unknown";
|
||||
|
||||
/**
|
||||
* Testing API provided by webconsole/net/utils/json.js
|
||||
*/
|
||||
function run_test() {
|
||||
// parseJSONString
|
||||
equal(parseJSONString(simpleJson).name, "John");
|
||||
equal(parseJSONString(jsonInFunc).name, "John");
|
||||
|
||||
// isJSON
|
||||
equal(isJSON(textMimeType, json1), true);
|
||||
equal(isJSON(textMimeType, json2), true);
|
||||
equal(isJSON(jsonMimeType, json3), true);
|
||||
equal(isJSON(jsonMimeType, json4), true);
|
||||
|
||||
equal(isJSON(unknownMimeType, json1), true);
|
||||
equal(isJSON(textMimeType, json1), true);
|
||||
|
||||
equal(isJSON(unknownMimeType), false);
|
||||
equal(isJSON(unknownMimeType, json5), false);
|
||||
}
|
||||
77
devtools/client/webconsole/net/test/unit/test_net-utils.js
Normal file
77
devtools/client/webconsole/net/test/unit/test_net-utils.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var Cu = Components.utils;
|
||||
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const {
|
||||
isImage,
|
||||
isHTML,
|
||||
getHeaderValue,
|
||||
isURLEncodedRequest,
|
||||
isMultiPartRequest
|
||||
} = require("devtools/client/webconsole/net/utils/net");
|
||||
|
||||
// Test data
|
||||
const imageMimeTypes = ["image/jpeg", "image/jpg", "image/gif",
|
||||
"image/png", "image/bmp"];
|
||||
|
||||
const htmlMimeTypes = ["text/html", "text/xml", "application/xml",
|
||||
"application/rss+xml", "application/atom+xml", "application/xhtml+xml",
|
||||
"application/mathml+xml", "application/rdf+xml"];
|
||||
|
||||
const headers = [{name: "headerName", value: "value1"}];
|
||||
|
||||
const har1 = {
|
||||
request: {
|
||||
postData: {
|
||||
text: "content-type: application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const har2 = {
|
||||
request: {
|
||||
headers: [{
|
||||
name: "content-type",
|
||||
value: "application/x-www-form-urlencoded"
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const har3 = {
|
||||
request: {
|
||||
headers: [{
|
||||
name: "content-type",
|
||||
value: "multipart/form-data"
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Testing API provided by webconsole/net/utils/net.js
|
||||
*/
|
||||
function run_test() {
|
||||
// isImage
|
||||
imageMimeTypes.forEach(mimeType => {
|
||||
ok(isImage(mimeType));
|
||||
});
|
||||
|
||||
// isHTML
|
||||
htmlMimeTypes.forEach(mimeType => {
|
||||
ok(isHTML(mimeType));
|
||||
});
|
||||
|
||||
// getHeaderValue
|
||||
equal(getHeaderValue(headers, "headerName"), "value1");
|
||||
|
||||
// isURLEncodedRequest
|
||||
ok(isURLEncodedRequest(har1));
|
||||
ok(isURLEncodedRequest(har2));
|
||||
|
||||
// isMultiPartRequest
|
||||
ok(isMultiPartRequest(har3));
|
||||
}
|
||||
9
devtools/client/webconsole/net/test/unit/xpcshell.ini
Normal file
9
devtools/client/webconsole/net/test/unit/xpcshell.ini
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
head =
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_json-utils.js]
|
||||
[test_net-utils.js]
|
||||
21
devtools/client/webconsole/net/utils/events.js
Normal file
21
devtools/client/webconsole/net/utils/events.js
Normal file
|
|
@ -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/. */
|
||||
"use strict";
|
||||
|
||||
function isLeftClick(event, allowKeyModifiers) {
|
||||
return event.button === 0 && (allowKeyModifiers || noKeyModifiers(event));
|
||||
}
|
||||
|
||||
function noKeyModifiers(event) {
|
||||
return !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey;
|
||||
}
|
||||
|
||||
function cancelEvent(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.isLeftClick = isLeftClick;
|
||||
exports.cancelEvent = cancelEvent;
|
||||
234
devtools/client/webconsole/net/utils/json.js
Normal file
234
devtools/client/webconsole/net/utils/json.js
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/* 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";
|
||||
|
||||
// List of JSON content types.
|
||||
const contentTypes = {
|
||||
"text/plain": 1,
|
||||
"text/javascript": 1,
|
||||
"text/x-javascript": 1,
|
||||
"text/json": 1,
|
||||
"text/x-json": 1,
|
||||
"application/json": 1,
|
||||
"application/x-json": 1,
|
||||
"application/javascript": 1,
|
||||
"application/x-javascript": 1,
|
||||
"application/json-rpc": 1
|
||||
};
|
||||
|
||||
// Implementation
|
||||
var Json = {};
|
||||
|
||||
/**
|
||||
* Parsing JSON
|
||||
*/
|
||||
Json.parseJSONString = function (jsonString) {
|
||||
if (!jsonString.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let regex, matches;
|
||||
|
||||
let first = firstNonWs(jsonString);
|
||||
if (first !== "[" && first !== "{") {
|
||||
// This (probably) isn't pure JSON. Let's try to strip various sorts
|
||||
// of XSSI protection/wrapping and see if that works better.
|
||||
|
||||
// Prototype-style secure requests
|
||||
regex = /^\s*\/\*-secure-([\s\S]*)\*\/\s*$/;
|
||||
matches = regex.exec(jsonString);
|
||||
if (matches) {
|
||||
jsonString = matches[1];
|
||||
|
||||
if (jsonString[0] === "\\" && jsonString[1] === "n") {
|
||||
jsonString = jsonString.substr(2);
|
||||
}
|
||||
|
||||
if (jsonString[jsonString.length - 2] === "\\" &&
|
||||
jsonString[jsonString.length - 1] === "n") {
|
||||
jsonString = jsonString.substr(0, jsonString.length - 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Google-style (?) delimiters
|
||||
if (jsonString.indexOf("&&&START&&&") !== -1) {
|
||||
regex = /&&&START&&&([\s\S]*)&&&END&&&/;
|
||||
matches = regex.exec(jsonString);
|
||||
if (matches) {
|
||||
jsonString = matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
// while(1);, for(;;);, and )]}'
|
||||
regex = /^\s*(\)\]\}[^\n]*\n|while\s*\(1\);|for\s*\(;;\);)([\s\S]*)/;
|
||||
matches = regex.exec(jsonString);
|
||||
if (matches) {
|
||||
jsonString = matches[2];
|
||||
}
|
||||
|
||||
// JSONP
|
||||
regex = /^\s*([A-Za-z0-9_$.]+\s*(?:\[.*\]|))\s*\(([\s\S]*)\)/;
|
||||
matches = regex.exec(jsonString);
|
||||
if (matches) {
|
||||
jsonString = matches[2];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
} catch (err) {
|
||||
// eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
// Give up if we don't have valid start, to avoid some unnecessary overhead.
|
||||
first = firstNonWs(jsonString);
|
||||
if (first !== "[" && first !== "{" && isNaN(first) && first !== '"') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove JavaScript comments, quote non-quoted identifiers, and merge
|
||||
// multi-line structures like |{"a": 1} \n {"b": 2}| into a single JSON
|
||||
// object [{"a": 1}, {"b": 2}].
|
||||
jsonString = pseudoJsonToJson(jsonString);
|
||||
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
} catch (err) {
|
||||
// eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
function firstNonWs(str) {
|
||||
for (let i = 0, len = str.length; i < len; i++) {
|
||||
let ch = str[i];
|
||||
if (ch !== " " && ch !== "\n" && ch !== "\t" && ch !== "\r") {
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function pseudoJsonToJson(json) {
|
||||
let ret = "";
|
||||
let at = 0, lasti = 0, lastch = "", hasMultipleParts = false;
|
||||
for (let i = 0, len = json.length; i < len; ++i) {
|
||||
let ch = json[i];
|
||||
if (/\s/.test(ch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === '"') {
|
||||
// Consume a string.
|
||||
++i;
|
||||
while (i < len) {
|
||||
if (json[i] === "\\") {
|
||||
++i;
|
||||
} else if (json[i] === '"') {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
} else if (ch === "'") {
|
||||
// Convert an invalid string into a valid one.
|
||||
ret += json.slice(at, i) + "\"";
|
||||
at = i + 1;
|
||||
++i;
|
||||
|
||||
while (i < len) {
|
||||
if (json[i] === "\\") {
|
||||
++i;
|
||||
} else if (json[i] === "'") {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i < len) {
|
||||
ret += json.slice(at, i) + "\"";
|
||||
at = i + 1;
|
||||
}
|
||||
} else if ((ch === "[" || ch === "{") &&
|
||||
(lastch === "]" || lastch === "}")) {
|
||||
// Multiple JSON messages in one... Make it into a single array by
|
||||
// inserting a comma and setting the "multiple parts" flag.
|
||||
ret += json.slice(at, i) + ",";
|
||||
hasMultipleParts = true;
|
||||
at = i;
|
||||
} else if (lastch === "," && (ch === "]" || ch === "}")) {
|
||||
// Trailing commas in arrays/objects.
|
||||
ret += json.slice(at, lasti);
|
||||
at = i;
|
||||
} else if (lastch === "/" && lasti === i - 1) {
|
||||
// Some kind of comment; remove it.
|
||||
if (ch === "/") {
|
||||
ret += json.slice(at, i - 1);
|
||||
at = i + json.slice(i).search(/\n|\r|$/);
|
||||
i = at - 1;
|
||||
} else if (ch === "*") {
|
||||
ret += json.slice(at, i - 1);
|
||||
at = json.indexOf("*/", i + 1) + 2;
|
||||
if (at === 1) {
|
||||
at = len;
|
||||
}
|
||||
i = at - 1;
|
||||
}
|
||||
ch = "\0";
|
||||
} else if (/[a-zA-Z$_]/.test(ch) && lastch !== ":") {
|
||||
// Non-quoted identifier. Quote it.
|
||||
ret += json.slice(at, i) + "\"";
|
||||
at = i;
|
||||
i = i + json.slice(i).search(/[^a-zA-Z0-9$_]|$/);
|
||||
ret += json.slice(at, i) + "\"";
|
||||
at = i;
|
||||
}
|
||||
|
||||
lastch = ch;
|
||||
lasti = i;
|
||||
}
|
||||
|
||||
ret += json.slice(at);
|
||||
if (hasMultipleParts) {
|
||||
ret = "[" + ret + "]";
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Json.isJSON = function (contentType, data) {
|
||||
// Workaround for JSON responses without proper content type
|
||||
// Let's consider all responses starting with "{" as JSON. In the worst
|
||||
// case there will be an exception when parsing. This means that no-JSON
|
||||
// responses (and post data) (with "{") can be parsed unnecessarily,
|
||||
// which represents a little overhead, but this happens only if the request
|
||||
// is actually expanded by the user in the UI (Net & Console panels).
|
||||
// Do a manual string search instead of checking (data.strip()[0] === "{")
|
||||
// to improve performance/memory usage.
|
||||
let len = data ? data.length : 0;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let ch = data.charAt(i);
|
||||
if (ch === "{") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") {
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!contentType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
contentType = contentType.split(";")[0];
|
||||
contentType = contentType.trim();
|
||||
return !!contentTypes[contentType];
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
module.exports = Json;
|
||||
|
||||
11
devtools/client/webconsole/net/utils/moz.build
Normal file
11
devtools/client/webconsole/net/utils/moz.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# -*- 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(
|
||||
'events.js',
|
||||
'json.js',
|
||||
'net.js',
|
||||
)
|
||||
134
devtools/client/webconsole/net/utils/net.js
Normal file
134
devtools/client/webconsole/net/utils/net.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/* 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 mimeCategoryMap = {
|
||||
"text/plain": "txt",
|
||||
"application/octet-stream": "bin",
|
||||
"text/html": "html",
|
||||
"text/xml": "html",
|
||||
"application/xml": "html",
|
||||
"application/rss+xml": "html",
|
||||
"application/atom+xml": "html",
|
||||
"application/xhtml+xml": "html",
|
||||
"application/mathml+xml": "html",
|
||||
"application/rdf+xml": "html",
|
||||
"text/css": "css",
|
||||
"application/x-javascript": "js",
|
||||
"text/javascript": "js",
|
||||
"application/javascript": "js",
|
||||
"text/ecmascript": "js",
|
||||
"application/ecmascript": "js",
|
||||
"image/jpeg": "image",
|
||||
"image/jpg": "image",
|
||||
"image/gif": "image",
|
||||
"image/png": "image",
|
||||
"image/bmp": "image",
|
||||
"application/x-shockwave-flash": "plugin",
|
||||
"application/x-silverlight-app": "plugin",
|
||||
"video/x-flv": "media",
|
||||
"audio/mpeg3": "media",
|
||||
"audio/x-mpeg-3": "media",
|
||||
"video/mpeg": "media",
|
||||
"video/x-mpeg": "media",
|
||||
"video/webm": "media",
|
||||
"video/mp4": "media",
|
||||
"video/ogg": "media",
|
||||
"audio/ogg": "media",
|
||||
"application/ogg": "media",
|
||||
"application/x-ogg": "media",
|
||||
"application/x-midi": "media",
|
||||
"audio/midi": "media",
|
||||
"audio/x-mid": "media",
|
||||
"audio/x-midi": "media",
|
||||
"music/crescendo": "media",
|
||||
"audio/wav": "media",
|
||||
"audio/x-wav": "media",
|
||||
"application/x-woff": "font",
|
||||
"application/font-woff": "font",
|
||||
"application/x-font-woff": "font",
|
||||
"application/x-ttf": "font",
|
||||
"application/x-font-ttf": "font",
|
||||
"font/ttf": "font",
|
||||
"font/woff": "font",
|
||||
"application/x-otf": "font",
|
||||
"application/x-font-otf": "font"
|
||||
};
|
||||
|
||||
var NetUtils = {};
|
||||
|
||||
NetUtils.isImage = function (contentType) {
|
||||
if (!contentType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
contentType = contentType.split(";")[0];
|
||||
contentType = contentType.trim();
|
||||
return mimeCategoryMap[contentType] == "image";
|
||||
};
|
||||
|
||||
NetUtils.isHTML = function (contentType) {
|
||||
if (!contentType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
contentType = contentType.split(";")[0];
|
||||
contentType = contentType.trim();
|
||||
return mimeCategoryMap[contentType] == "html";
|
||||
};
|
||||
|
||||
NetUtils.getHeaderValue = function (headers, name) {
|
||||
if (!headers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
name = name.toLowerCase();
|
||||
for (let i = 0; i < headers.length; ++i) {
|
||||
let headerName = headers[i].name.toLowerCase();
|
||||
if (headerName == name) {
|
||||
return headers[i].value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NetUtils.parseXml = function (content) {
|
||||
let contentType = content.mimeType.split(";")[0];
|
||||
contentType = contentType.trim();
|
||||
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(content.text, contentType);
|
||||
let root = doc.documentElement;
|
||||
|
||||
// Error handling
|
||||
let nsURI = "http://www.mozilla.org/newlayout/xml/parsererror.xml";
|
||||
if (root.namespaceURI == nsURI && root.nodeName == "parsererror") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return doc;
|
||||
};
|
||||
|
||||
NetUtils.isURLEncodedRequest = function (file) {
|
||||
let mimeType = "application/x-www-form-urlencoded";
|
||||
|
||||
let postData = file.request.postData;
|
||||
if (postData && postData.text) {
|
||||
let text = postData.text.toLowerCase();
|
||||
if (text.startsWith("content-type: " + mimeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
let value = NetUtils.getHeaderValue(file.request.headers, "content-type");
|
||||
return value && value.startsWith(mimeType);
|
||||
};
|
||||
|
||||
NetUtils.isMultiPartRequest = function (file) {
|
||||
let mimeType = "multipart/form-data";
|
||||
let value = NetUtils.getHeaderValue(file.request.headers, "content-type");
|
||||
return value && value.startsWith(mimeType);
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
module.exports = NetUtils;
|
||||
Loading…
Add table
Add a link
Reference in a new issue