CookiesPanel.js (6350B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 Component, 9 createFactory, 10 } = require("resource://devtools/client/shared/vendor/react.mjs"); 11 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 12 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 13 const { 14 L10N, 15 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js"); 16 const { 17 fetchNetworkUpdatePacket, 18 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 19 const { 20 sortObjectKeys, 21 } = require("resource://devtools/client/netmonitor/src/utils/sort-utils.js"); 22 const { 23 FILTER_SEARCH_DELAY, 24 } = require("resource://devtools/client/netmonitor/src/constants.js"); 25 26 // Component 27 const PropertiesView = createFactory( 28 require("resource://devtools/client/netmonitor/src/components/request-details/PropertiesView.js") 29 ); 30 const SearchBox = createFactory( 31 require("resource://devtools/client/shared/components/SearchBox.js") 32 ); 33 const Accordion = createFactory( 34 require("resource://devtools/client/shared/components/Accordion.js") 35 ); 36 37 loader.lazyGetter(this, "TreeRow", function () { 38 return createFactory( 39 ChromeUtils.importESModule( 40 "resource://devtools/client/shared/components/tree/TreeRow.mjs", 41 { global: "current" } 42 ).default 43 ); 44 }); 45 46 const { div } = dom; 47 48 const COOKIES_EMPTY_TEXT = L10N.getStr("cookiesEmptyText"); 49 const COOKIES_FILTER_TEXT = L10N.getStr("cookiesFilterText"); 50 const REQUEST_COOKIES = L10N.getStr("requestCookies"); 51 const RESPONSE_COOKIES = L10N.getStr("responseCookies"); 52 53 /* 54 * Cookies panel component 55 * This tab lists full details of any cookies sent with the request or response 56 */ 57 class CookiesPanel extends Component { 58 static get propTypes() { 59 return { 60 connector: PropTypes.object.isRequired, 61 openLink: PropTypes.func, 62 request: PropTypes.object.isRequired, 63 targetSearchResult: PropTypes.object, 64 }; 65 } 66 67 constructor(props) { 68 super(props); 69 this.state = { 70 filterText: "", 71 }; 72 } 73 74 componentDidMount() { 75 const { connector, request } = this.props; 76 fetchNetworkUpdatePacket(connector.requestData, request, [ 77 "requestCookies", 78 "responseCookies", 79 ]); 80 } 81 82 // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 83 UNSAFE_componentWillReceiveProps(nextProps) { 84 const { connector, request } = nextProps; 85 fetchNetworkUpdatePacket(connector.requestData, request, [ 86 "requestCookies", 87 "responseCookies", 88 ]); 89 } 90 91 /** 92 * Mapping array to dict for TreeView usage. 93 * Since TreeView only support Object(dict) format. 94 * 95 * @param {object[]} arr - key-value pair array like cookies or params 96 * @returns {object} 97 */ 98 getProperties(arr, title) { 99 const cookies = arr.reduce((map, obj) => { 100 // Generally cookies object contains only name and value properties and can 101 // be rendered as name: value pair. 102 // When there are more properties in cookies object such as extra or path, 103 // We will pass the object to display these extra information 104 if (Object.keys(obj).length > 2) { 105 map[obj.name] = Object.assign({}, obj); 106 delete map[obj.name].name; 107 } else { 108 map[obj.name] = obj.value; 109 } 110 return map; 111 }, Object.create(null)); 112 113 // To have different roots for Request and Response cookies 114 return { [title]: cookies }; 115 } 116 117 /** 118 * Custom rendering method passed to PropertiesView. It's 119 * responsible to filter out level 0 node in the tree 120 * 121 * @param {object} props 122 */ 123 renderRow(props) { 124 const { level } = props.member; 125 126 if (level === 0) { 127 return null; 128 } 129 130 return TreeRow(props); 131 } 132 133 /** 134 * Get the selected cookies path 135 * 136 * @param {object} searchResult 137 * @returns {string} 138 */ 139 getTargetCookiePath(searchResult) { 140 if (!searchResult) { 141 return null; 142 } 143 144 switch (searchResult.type) { 145 case "requestCookies": { 146 return `/${REQUEST_COOKIES}/${searchResult.label}`; 147 } 148 case "responseCookies": 149 return `/${RESPONSE_COOKIES}/${searchResult.label}`; 150 } 151 152 return null; 153 } 154 155 render() { 156 let { 157 request: { 158 requestCookies = { cookies: [] }, 159 responseCookies = { cookies: [] }, 160 }, 161 targetSearchResult, 162 } = this.props; 163 164 const { filterText } = this.state; 165 166 requestCookies = requestCookies.cookies || requestCookies; 167 responseCookies = responseCookies.cookies || responseCookies; 168 169 if (!requestCookies.length && !responseCookies.length) { 170 return div({ className: "empty-notice" }, COOKIES_EMPTY_TEXT); 171 } 172 173 const items = []; 174 175 if (responseCookies.length) { 176 items.push({ 177 component: PropertiesView, 178 componentProps: { 179 object: sortObjectKeys( 180 this.getProperties(responseCookies, RESPONSE_COOKIES) 181 ), 182 filterText, 183 targetSearchResult, 184 defaultSelectFirstNode: false, 185 selectPath: this.getTargetCookiePath, 186 renderRow: this.renderRow, 187 }, 188 header: RESPONSE_COOKIES, 189 id: "responseCookies", 190 opened: true, 191 }); 192 } 193 194 if (requestCookies.length) { 195 items.push({ 196 component: PropertiesView, 197 componentProps: { 198 object: sortObjectKeys( 199 this.getProperties(requestCookies, REQUEST_COOKIES) 200 ), 201 filterText, 202 targetSearchResult, 203 defaultSelectFirstNode: false, 204 selectPath: this.getTargetCookiePath, 205 renderRow: this.renderRow, 206 }, 207 header: REQUEST_COOKIES, 208 id: "requestCookies", 209 opened: true, 210 }); 211 } 212 213 return div( 214 { className: "panel-container cookies-panel-container" }, 215 div( 216 { className: "devtools-toolbar devtools-input-toolbar" }, 217 SearchBox({ 218 delay: FILTER_SEARCH_DELAY, 219 type: "filter", 220 onChange: text => this.setState({ filterText: text }), 221 placeholder: COOKIES_FILTER_TEXT, 222 }) 223 ), 224 Accordion({ items }) 225 ); 226 } 227 } 228 229 module.exports = CookiesPanel;