Source code

Revision control

Copy as Markdown

Other Tools

/* 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 {
Component,
createFactory,
} = require("resource://devtools/client/shared/vendor/react.js");
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
const {
L10N,
} = require("resource://devtools/client/netmonitor/src/utils/l10n.js");
const {
fetchNetworkUpdatePacket,
} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
const {
sortObjectKeys,
} = require("resource://devtools/client/netmonitor/src/utils/sort-utils.js");
const {
FILTER_SEARCH_DELAY,
} = require("resource://devtools/client/netmonitor/src/constants.js");
// Component
const PropertiesView = createFactory(
require("resource://devtools/client/netmonitor/src/components/request-details/PropertiesView.js")
);
const SearchBox = createFactory(
require("resource://devtools/client/shared/components/SearchBox.js")
);
const Accordion = createFactory(
require("resource://devtools/client/shared/components/Accordion.js")
);
loader.lazyGetter(this, "TreeRow", function () {
return createFactory(
require("resource://devtools/client/shared/components/tree/TreeRow.js")
);
});
const { div } = dom;
const COOKIES_EMPTY_TEXT = L10N.getStr("cookiesEmptyText");
const COOKIES_FILTER_TEXT = L10N.getStr("cookiesFilterText");
const REQUEST_COOKIES = L10N.getStr("requestCookies");
const RESPONSE_COOKIES = L10N.getStr("responseCookies");
/*
* Cookies panel component
* This tab lists full details of any cookies sent with the request or response
*/
class CookiesPanel extends Component {
static get propTypes() {
return {
connector: PropTypes.object.isRequired,
openLink: PropTypes.func,
request: PropTypes.object.isRequired,
targetSearchResult: PropTypes.object,
};
}
constructor(props) {
super(props);
this.state = {
filterText: "",
};
}
componentDidMount() {
const { connector, request } = this.props;
fetchNetworkUpdatePacket(connector.requestData, request, [
"requestCookies",
"responseCookies",
]);
}
UNSAFE_componentWillReceiveProps(nextProps) {
const { connector, request } = nextProps;
fetchNetworkUpdatePacket(connector.requestData, request, [
"requestCookies",
"responseCookies",
]);
}
/**
* Mapping array to dict for TreeView usage.
* Since TreeView only support Object(dict) format.
*
* @param {Object[]} arr - key-value pair array like cookies or params
* @returns {Object}
*/
getProperties(arr, title) {
const cookies = arr.reduce((map, obj) => {
// Generally cookies object contains only name and value properties and can
// be rendered as name: value pair.
// When there are more properties in cookies object such as extra or path,
// We will pass the object to display these extra information
if (Object.keys(obj).length > 2) {
map[obj.name] = Object.assign({}, obj);
delete map[obj.name].name;
} else {
map[obj.name] = obj.value;
}
return map;
}, Object.create(null));
// To have different roots for Request and Response cookies
return { [title]: cookies };
}
/**
* Custom rendering method passed to PropertiesView. It's
* responsible to filter out level 0 node in the tree
*
* @param {Object} props
*/
renderRow(props) {
const { level } = props.member;
if (level === 0) {
return null;
}
return TreeRow(props);
}
/**
* Get the selected cookies path
* @param {Object} searchResult
* @returns {string}
*/
getTargetCookiePath(searchResult) {
if (!searchResult) {
return null;
}
switch (searchResult.type) {
case "requestCookies": {
return `/${REQUEST_COOKIES}/${searchResult.label}`;
}
case "responseCookies":
return `/${RESPONSE_COOKIES}/${searchResult.label}`;
}
return null;
}
render() {
let {
request: {
requestCookies = { cookies: [] },
responseCookies = { cookies: [] },
},
targetSearchResult,
} = this.props;
const { filterText } = this.state;
requestCookies = requestCookies.cookies || requestCookies;
responseCookies = responseCookies.cookies || responseCookies;
if (!requestCookies.length && !responseCookies.length) {
return div({ className: "empty-notice" }, COOKIES_EMPTY_TEXT);
}
const items = [];
if (responseCookies.length) {
items.push({
component: PropertiesView,
componentProps: {
object: sortObjectKeys(
this.getProperties(responseCookies, RESPONSE_COOKIES)
),
filterText,
targetSearchResult,
defaultSelectFirstNode: false,
selectPath: this.getTargetCookiePath,
renderRow: this.renderRow,
},
header: RESPONSE_COOKIES,
id: "responseCookies",
opened: true,
});
}
if (requestCookies.length) {
items.push({
component: PropertiesView,
componentProps: {
object: sortObjectKeys(
this.getProperties(requestCookies, REQUEST_COOKIES)
),
filterText,
targetSearchResult,
defaultSelectFirstNode: false,
selectPath: this.getTargetCookiePath,
renderRow: this.renderRow,
},
header: REQUEST_COOKIES,
id: "requestCookies",
opened: true,
});
}
return div(
{ className: "panel-container cookies-panel-container" },
div(
{ className: "devtools-toolbar devtools-input-toolbar" },
SearchBox({
delay: FILTER_SEARCH_DELAY,
type: "filter",
onChange: text => this.setState({ filterText: text }),
placeholder: COOKIES_FILTER_TEXT,
})
),
Accordion({ items })
);
}
}
module.exports = CookiesPanel;