diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/netmonitor/src/components/CustomRequestPanel.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/netmonitor/src/components/CustomRequestPanel.js')
-rw-r--r-- | devtools/client/netmonitor/src/components/CustomRequestPanel.js | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/components/CustomRequestPanel.js b/devtools/client/netmonitor/src/components/CustomRequestPanel.js new file mode 100644 index 0000000000..66f63b7d8d --- /dev/null +++ b/devtools/client/netmonitor/src/components/CustomRequestPanel.js @@ -0,0 +1,371 @@ +/* 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, +} = 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 { + connect, +} = require("resource://devtools/client/shared/redux/visibility-handler-connect.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 Actions = require("resource://devtools/client/netmonitor/src/actions/index.js"); +const { + getSelectedRequest, +} = require("resource://devtools/client/netmonitor/src/selectors/index.js"); +const { + getUrlQuery, + parseQueryString, + writeHeaderText, +} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); + +const { button, div, input, label, textarea } = dom; + +const CUSTOM_CANCEL = L10N.getStr("netmonitor.custom.cancel"); +const CUSTOM_HEADERS = L10N.getStr("netmonitor.custom.headers"); +const CUSTOM_NEW_REQUEST = L10N.getStr("netmonitor.custom.newRequest"); +const CUSTOM_NEW_REQUEST_METHOD_LABEL = L10N.getStr( + "netmonitor.custom.newRequestMethodLabel" +); +const CUSTOM_NEW_REQUEST_URL_LABEL = L10N.getStr( + "netmonitor.custom.newRequestUrlLabel" +); +const CUSTOM_POSTDATA = L10N.getStr("netmonitor.custom.postData"); +const CUSTOM_QUERY = L10N.getStr("netmonitor.custom.query"); +const CUSTOM_SEND = L10N.getStr("netmonitor.custom.send"); + +/* + * Custom request panel component + * A network request editor which simply provide edit and resend interface + * for network development. + */ +class CustomRequestPanel extends Component { + static get propTypes() { + return { + connector: PropTypes.object.isRequired, + removeSelectedCustomRequest: PropTypes.func.isRequired, + request: PropTypes.object.isRequired, + sendCustomRequest: PropTypes.func.isRequired, + updateRequest: PropTypes.func.isRequired, + }; + } + + componentDidMount() { + const { request, connector } = this.props; + this.initialRequestMethod = request.method; + fetchNetworkUpdatePacket(connector.requestData, request, [ + "requestHeaders", + "responseHeaders", + "requestPostData", + ]); + } + + // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 + UNSAFE_componentWillReceiveProps(nextProps) { + const { request, connector } = nextProps; + fetchNetworkUpdatePacket(connector.requestData, request, [ + "requestHeaders", + "responseHeaders", + "requestPostData", + ]); + } + + /** + * Parse a text representation of a name[divider]value list with + * the given name regex and divider character. + * + * @param {string} text - Text of list + * @return {array} array of headers info {name, value} + */ + parseRequestText(text, namereg, divider) { + const regex = new RegExp(`(${namereg})\\${divider}\\s*(\\S.*)`); + const pairs = []; + + for (const line of text.split("\n")) { + const matches = regex.exec(line); + if (matches) { + const [, name, value] = matches; + pairs.push({ name, value }); + } + } + return pairs; + } + + /** + * Update Custom Request Fields + * + * @param {Object} evt click event + * @param {Object} request current request + * @param {updateRequest} updateRequest action + */ + updateCustomRequestFields(evt, request, updateRequest) { + const val = evt.target.value; + let data; + + switch (evt.target.id) { + case "custom-headers-value": + data = { + requestHeaders: { + customHeadersValue: val || "", + // Parse text representation of multiple HTTP headers + headers: this.parseRequestText(val, "\\S+?", ":"), + }, + }; + break; + case "custom-method-value": + // If val is empty when leaving the "method" field, set the method to + // its original value + data = + evt.type === "blur" && val === "" + ? { method: this.initialRequestMethod } + : { method: val.trim() }; + break; + case "custom-postdata-value": + // Update "content-length" header value to reflect change + // in post data field. + const { requestHeaders } = request; + const newHeaders = requestHeaders.headers.map(header => { + if (header.name.toLowerCase() == "content-length") { + return { + name: header.name, + value: val.length, + }; + } + return header; + }); + + data = { + requestPostData: { + postData: { text: val }, + }, + requestHeaders: { + headers: newHeaders, + }, + }; + break; + case "custom-query-value": + let customQueryValue = val || ""; + // Parse readable text list of a query string + const queryArray = customQueryValue + ? this.parseRequestText(customQueryValue, ".+?", "=") + : []; + // Write out a list of query params into a query string + const queryString = queryArray + .map(({ name, value }) => name + "=" + value) + .join("&"); + const url = queryString + ? [request.url.split("?")[0], queryString].join("?") + : request.url.split("?")[0]; + // Remove temp customQueryValue while query string is parsable + if ( + customQueryValue === "" || + queryArray.length === customQueryValue.split("\n").length + ) { + customQueryValue = null; + } + data = { + customQueryValue, + url, + }; + break; + case "custom-url-value": + data = { + customQueryValue: null, + url: val, + }; + break; + default: + break; + } + if (data) { + // All updateRequest batch mode should be disabled to make UI editing in sync + updateRequest(request.id, data, false); + } + } + + render() { + const { + removeSelectedCustomRequest, + request = {}, + sendCustomRequest, + updateRequest, + } = this.props; + const { method, customQueryValue, requestHeaders, requestPostData, url } = + request; + + let headers = ""; + if (requestHeaders) { + headers = requestHeaders.customHeadersValue + ? requestHeaders.customHeadersValue + : writeHeaderText(requestHeaders.headers).trim(); + } + const queryArray = url ? parseQueryString(getUrlQuery(url)) : []; + let params = customQueryValue; + if (!params) { + params = queryArray + ? queryArray.map(({ name, value }) => name + "=" + value).join("\n") + : ""; + } + const postData = requestPostData?.postData.text + ? requestPostData.postData.text + : ""; + + return div( + { className: "custom-request-panel" }, + div( + { className: "custom-request-label custom-header" }, + CUSTOM_NEW_REQUEST + ), + div( + { className: "custom-request-panel-content" }, + div( + { className: "tabpanel-summary-container custom-request" }, + div( + { className: "custom-request-button-container" }, + button( + { + className: "devtools-button", + id: "custom-request-close-button", + onClick: removeSelectedCustomRequest, + }, + CUSTOM_CANCEL + ), + button( + { + className: "devtools-button", + id: "custom-request-send-button", + onClick: sendCustomRequest, + }, + CUSTOM_SEND + ) + ) + ), + div( + { + className: "tabpanel-summary-container custom-method-and-url", + id: "custom-method-and-url", + }, + label( + { + className: "custom-method-value-label custom-request-label", + htmlFor: "custom-method-value", + }, + CUSTOM_NEW_REQUEST_METHOD_LABEL + ), + input({ + className: "custom-method-value", + id: "custom-method-value", + onChange: evt => + this.updateCustomRequestFields(evt, request, updateRequest), + onBlur: evt => + this.updateCustomRequestFields(evt, request, updateRequest), + value: method, + }), + label( + { + className: "custom-url-value-label custom-request-label", + htmlFor: "custom-url-value", + }, + CUSTOM_NEW_REQUEST_URL_LABEL + ), + input({ + className: "custom-url-value", + id: "custom-url-value", + onChange: evt => + this.updateCustomRequestFields(evt, request, updateRequest), + value: url || "http://", + }) + ), + // Hide query field when there is no params + params + ? div( + { + className: "tabpanel-summary-container custom-section", + id: "custom-query", + }, + label( + { + className: "custom-request-label", + htmlFor: "custom-query-value", + }, + CUSTOM_QUERY + ), + textarea({ + className: "tabpanel-summary-input", + id: "custom-query-value", + onChange: evt => + this.updateCustomRequestFields(evt, request, updateRequest), + rows: 4, + value: params, + wrap: "off", + }) + ) + : null, + div( + { + id: "custom-headers", + className: "tabpanel-summary-container custom-section", + }, + label( + { + className: "custom-request-label", + htmlFor: "custom-headers-value", + }, + CUSTOM_HEADERS + ), + textarea({ + className: "tabpanel-summary-input", + id: "custom-headers-value", + onChange: evt => + this.updateCustomRequestFields(evt, request, updateRequest), + rows: 8, + value: headers, + wrap: "off", + }) + ), + div( + { + id: "custom-postdata", + className: "tabpanel-summary-container custom-section", + }, + label( + { + className: "custom-request-label", + htmlFor: "custom-postdata-value", + }, + CUSTOM_POSTDATA + ), + textarea({ + className: "tabpanel-summary-input", + id: "custom-postdata-value", + onChange: evt => + this.updateCustomRequestFields(evt, request, updateRequest), + rows: 6, + value: postData, + wrap: "off", + }) + ) + ) + ); + } +} + +module.exports = connect( + state => ({ request: getSelectedRequest(state) }), + (dispatch, props) => ({ + removeSelectedCustomRequest: () => + dispatch(Actions.removeSelectedCustomRequest()), + sendCustomRequest: () => dispatch(Actions.sendCustomRequest()), + updateRequest: (id, data, batch) => + dispatch(Actions.updateRequest(id, data, batch)), + }) +)(CustomRequestPanel); |