diff options
Diffstat (limited to 'devtools/client/netmonitor/src/components/previews/UrlPreview.js')
-rw-r--r-- | devtools/client/netmonitor/src/components/previews/UrlPreview.js | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/components/previews/UrlPreview.js b/devtools/client/netmonitor/src/components/previews/UrlPreview.js new file mode 100644 index 0000000000..c20762912f --- /dev/null +++ b/devtools/client/netmonitor/src/components/previews/UrlPreview.js @@ -0,0 +1,290 @@ +/* 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 PropertiesView = createFactory( + require("resource://devtools/client/netmonitor/src/components/request-details/PropertiesView.js") +); +const { + L10N, +} = require("resource://devtools/client/netmonitor/src/utils/l10n.js"); +const { + parseQueryString, +} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); + +const TreeRow = createFactory( + require("resource://devtools/client/shared/components/tree/TreeRow.js") +); + +loader.lazyGetter(this, "MODE", function () { + return require("resource://devtools/client/shared/components/reps/index.js") + .MODE; +}); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); + +const { div, span, tr, td } = dom; + +/** + * Url Preview Component + * This component is used to render urls. Its show both compact and destructured views + * of the url. Its takes a url and the http method as properties. + * + * Example Url: + * https://foo.com/bla?x=123&y=456&z=789&a=foo&a=bar + * + * Structure: + * { + * GET : { + * "scheme" : "https", + * "host" : "foo.com", + * "filename" : "bla", + * "query" : { + * "x": "123", + * "y": "456", + * "z": "789", + * "a": { + * "0": foo, + * "1": bar + * } + * }, + * "remote" : { + * "address" : "127.0.0.1:8080" + * } + * } + * } + */ +class UrlPreview extends Component { + static get propTypes() { + return { + url: PropTypes.string, + method: PropTypes.string, + address: PropTypes.string, + shouldExpandPreview: PropTypes.bool, + onTogglePreview: PropTypes.func, + }; + } + + constructor(props) { + super(props); + this.parseUrl = this.parseUrl.bind(this); + this.renderValue = this.renderValue.bind(this); + } + + shouldComponentUpdate(nextProps) { + return ( + nextProps.url !== this.props.url || + nextProps.method !== this.props.method || + nextProps.address !== this.props.address + ); + } + + renderRow(props) { + const { + member: { name, level }, + } = props; + if ((name == "query" || name == "remote") && level == 1) { + return tr( + { key: name, className: "treeRow stringRow" }, + td( + { colSpan: 2, className: "splitter" }, + div({ className: "horizontal-splitter" }) + ) + ); + } + + const customProps = { ...props }; + customProps.member.selected = false; + return TreeRow(customProps); + } + + renderValue(props) { + const { + member: { level, open }, + value, + } = props; + if (level == 0) { + if (open) { + return ""; + } + const { scheme, host, filename, query } = value; + const queryParamNames = query ? Object.keys(query) : []; + // render collapsed url + return div( + { key: "url", className: "url" }, + span({ key: "url-scheme", className: "url-scheme" }, `${scheme}://`), + span({ key: "url-host", className: "url-host" }, `${host}`), + span({ key: "url-filename", className: "url-filename" }, `${filename}`), + !!queryParamNames.length && + span({ key: "url-ques", className: "url-chars" }, "?"), + + queryParamNames.map((name, index) => { + if (Array.isArray(query[name])) { + return query[name].map((item, queryIndex) => { + return span( + { + key: `url-params-${name}${queryIndex}`, + className: "url-params", + }, + span( + { + key: `url-params${name}${queryIndex}-name`, + className: "url-params-name", + }, + `${name}` + ), + span( + { + key: `url-chars-${name}${queryIndex}-equals`, + className: "url-chars", + }, + "=" + ), + span( + { + key: `url-params-${name}${queryIndex}-value`, + className: "url-params-value", + }, + `${item}` + ), + (query[name].length - 1 !== queryIndex || + queryParamNames.length - 1 !== index) && + span({ key: "url-amp", className: "url-chars" }, "&") + ); + }); + } + + return span( + { key: `url-params-${name}`, className: "url-params" }, + span( + { key: "url-params-name", className: "url-params-name" }, + `${name}` + ), + span({ key: "url-chars-equals", className: "url-chars" }, "="), + span( + { key: "url-params-value", className: "url-params-value" }, + `${query[name]}` + ), + queryParamNames.length - 1 !== index && + span({ key: "url-amp", className: "url-chars" }, "&") + ); + }) + ); + } + if (typeof value !== "string") { + // the query node would be an object + if (level == 0) { + return ""; + } + // for arrays (multival) + return "[...]"; + } + + return value; + } + + parseUrl(url) { + const { method, address } = this.props; + const { host, protocol, pathname, search } = new URL(url); + + const urlObject = { + [method]: { + scheme: protocol.replace(":", ""), + host, + filename: pathname, + }, + }; + + const expandedNodes = new Set(); + + // check and add query parameters + if (search.length) { + const params = parseQueryString(search); + // make sure the query node is always expanded + expandedNodes.add(`/${method}/query`); + urlObject[method].query = params.reduce((map, obj) => { + const value = map[obj.name]; + if (value || value === "") { + if (typeof value !== "object") { + expandedNodes.add(`/${method}/query/${obj.name}`); + map[obj.name] = [value]; + } + map[obj.name].push(obj.value); + } else { + map[obj.name] = obj.value; + } + return map; + }, Object.create(null)); + } + + if (address) { + // makes sure the remote adress section is expanded + expandedNodes.add(`/${method}/remote`); + urlObject[method].remote = { + [L10N.getStr("netmonitor.headers.address")]: address, + }; + } + + return { + urlObject, + expandedNodes, + }; + } + + render() { + const { + url, + method, + shouldExpandPreview = false, + onTogglePreview, + } = this.props; + + const { urlObject, expandedNodes } = this.parseUrl(url); + + if (shouldExpandPreview) { + expandedNodes.add(`/${method}`); + } + + return div( + { className: "url-preview" }, + PropertiesView({ + object: urlObject, + useQuotes: true, + defaultSelectFirstNode: false, + mode: MODE.TINY, + expandedNodes, + renderRow: this.renderRow, + renderValue: this.renderValue, + enableInput: false, + onClickRow: (path, evt, member) => { + // Only track when the root is toggled + // as all the others are always expanded by + // default. + if (path == `/${method}`) { + onTogglePreview(!member.open); + } + }, + contextMenuFormatters: { + copyFormatter: (member, baseCopyFormatter) => { + const { value, level, hasChildren } = member; + if (hasChildren && level == 0) { + const { scheme, filename, host, query } = value; + return `${scheme}://${host}${filename}${ + query ? "?" + new URLSearchParams(query).toString() : "" + }`; + } + return baseCopyFormatter(member); + }, + }, + }) + ); + } +} + +module.exports = UrlPreview; |