--- a/devtools/client/netmonitor/actions/requests.js
+++ b/devtools/client/netmonitor/actions/requests.js
@@ -1,20 +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/. */
+/* globals NetMonitorController */
"use strict";
+const { getSelectedRequest } = require("../selectors/index");
const {
ADD_REQUEST,
- UPDATE_REQUEST,
+ CLEAR_REQUESTS,
CLONE_SELECTED_REQUEST,
REMOVE_SELECTED_CUSTOM_REQUEST,
- CLEAR_REQUESTS,
+ SEND_CUSTOM_REQUEST,
+ UPDATE_REQUEST,
+ UPDATE_SELECTED_REQUEST,
+ UPDATE_SELECTED_REQUEST_HEADER,
+ UPDATE_SELECTED_REQUEST_QUERY,
} = require("../constants");
function addRequest(id, data, batch) {
return {
type: ADD_REQUEST,
id,
data,
meta: { batch },
@@ -25,27 +31,88 @@ function updateRequest(id, data, batch)
return {
type: UPDATE_REQUEST,
id,
data,
meta: { batch },
};
}
+function updateSelectedRequest(data, batch) {
+ return {
+ type: UPDATE_SELECTED_REQUEST,
+ data,
+ meta: { batch },
+ };
+}
+
+function updateSelectedRequestHeader(data, batch) {
+ return {
+ type: UPDATE_SELECTED_REQUEST_HEADER,
+ data,
+ meta: { batch },
+ };
+}
+
+function updateSelectedRequestQuery(data, batch) {
+ return {
+ type: UPDATE_SELECTED_REQUEST_QUERY,
+ data,
+ meta: { batch },
+ };
+}
+
/**
* Clone the currently selected request, set the "isCustom" attribute.
* Used by the "Edit and Resend" feature.
*/
function cloneSelectedRequest() {
return {
type: CLONE_SELECTED_REQUEST
};
}
/**
+ * Send a new HTTP request using the data in the custom request form.
+ */
+function sendCustomRequest() {
+ if (!NetMonitorController.supportsCustomRequest) {
+ return cloneSelectedRequest();
+ }
+
+ return (dispatch, getState) => {
+ const state = getState();
+ const selected = getSelectedRequest(state);
+
+ if (!selected) {
+ // avoid consistent-return eslint error by async return the action
+ () => cloneSelectedRequest();
+ }
+
+ // Send a new HTTP request using the data in the custom request form
+ let data = {
+ url: selected.url,
+ method: selected.method,
+ httpVersion: selected.httpVersion,
+ };
+ if (selected.requestHeaders) {
+ data.headers = selected.requestHeaders.headers;
+ }
+ if (selected.requestPostData) {
+ data.body = selected.requestPostData.postData.text;
+ }
+
+ NetMonitorController.webConsoleClient.sendHTTPRequest(data, (response) => ({
+ type: SEND_CUSTOM_REQUEST,
+ id: response.eventActor.actor,
+ }));
+ };
+}
+
+/**
* Remove a request from the list. Supports removing only cloned requests with a
* "isCustom" attribute. Other requests never need to be removed.
*/
function removeSelectedCustomRequest() {
return {
type: REMOVE_SELECTED_CUSTOM_REQUEST
};
}
@@ -53,13 +120,17 @@ function removeSelectedCustomRequest() {
function clearRequests() {
return {
type: CLEAR_REQUESTS
};
}
module.exports = {
addRequest,
- updateRequest,
+ clearRequests,
cloneSelectedRequest,
removeSelectedCustomRequest,
- clearRequests,
+ sendCustomRequest,
+ updateRequest,
+ updateSelectedRequest,
+ updateSelectedRequestHeader,
+ updateSelectedRequestQuery,
};
--- a/devtools/client/netmonitor/actions/selection.js
+++ b/devtools/client/netmonitor/actions/selection.js
@@ -1,28 +1,16 @@
/* 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 { getDisplayedRequests } = require("../selectors/index");
-const { SELECT_REQUEST, PRESELECT_REQUEST } = require("../constants");
-
-/**
- * When a new request with a given id is added in future, select it immediately.
- * Used by the "Edit and Resend" feature, where we know in advance the ID of the
- * request, at a time when it wasn't sent yet.
- */
-function preselectRequest(id) {
- return {
- type: PRESELECT_REQUEST,
- id
- };
-}
+const { SELECT_REQUEST } = require("../constants");
/**
* Select request with a given id.
*/
function selectRequest(id) {
return {
type: SELECT_REQUEST,
id
@@ -56,12 +44,11 @@ function selectDelta(delta) {
const newIndex = Math.min(Math.max(0, selIndex + delta), requests.size - 1);
const newItem = requests.get(newIndex);
dispatch(selectRequest(newItem.id));
};
}
module.exports = {
- preselectRequest,
selectRequest,
selectDelta,
};
--- a/devtools/client/netmonitor/constants.js
+++ b/devtools/client/netmonitor/constants.js
@@ -16,24 +16,27 @@ const actionTypes = {
BATCH_ACTIONS: "BATCH_ACTIONS",
BATCH_ENABLE: "BATCH_ENABLE",
CLEAR_REQUESTS: "CLEAR_REQUESTS",
CLEAR_TIMING_MARKERS: "CLEAR_TIMING_MARKERS",
CLONE_SELECTED_REQUEST: "CLONE_SELECTED_REQUEST",
ENABLE_REQUEST_FILTER_TYPE_ONLY: "ENABLE_REQUEST_FILTER_TYPE_ONLY",
OPEN_SIDEBAR: "OPEN_SIDEBAR",
OPEN_STATISTICS: "OPEN_STATISTICS",
- PRESELECT_REQUEST: "PRESELECT_REQUEST",
REMOVE_SELECTED_CUSTOM_REQUEST: "REMOVE_SELECTED_CUSTOM_REQUEST",
SELECT_REQUEST: "SELECT_REQUEST",
SELECT_DETAILS_PANEL_TAB: "SELECT_DETAILS_PANEL_TAB",
+ SEND_CUSTOM_REQUEST: "SEND_CUSTOM_REQUEST",
SET_REQUEST_FILTER_TEXT: "SET_REQUEST_FILTER_TEXT",
SORT_BY: "SORT_BY",
TOGGLE_REQUEST_FILTER_TYPE: "TOGGLE_REQUEST_FILTER_TYPE",
UPDATE_REQUEST: "UPDATE_REQUEST",
+ UPDATE_SELECTED_REQUEST: "UPDATE_SELECTED_REQUEST",
+ UPDATE_SELECTED_REQUEST_HEADER: "UPDATE_SELECTED_REQUEST_HEADER",
+ UPDATE_SELECTED_REQUEST_QUERY: "UPDATE_SELECTED_REQUEST_QUERY",
WATERFALL_RESIZE: "WATERFALL_RESIZE",
};
// Descriptions for what this frontend is currently doing.
const ACTIVITY_TYPE = {
// Standing by and handling requests normally.
NONE: 0,
deleted file mode 100644
--- a/devtools/client/netmonitor/custom-request-view.js
+++ /dev/null
@@ -1,222 +0,0 @@
-/* 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/. */
-
-/* globals window, dumpn, gNetwork, $, EVENTS, NetMonitorView */
-
-"use strict";
-
-const { Task } = require("devtools/shared/task");
-const { writeHeaderText,
- getKeyWithEvent,
- getUrlQuery,
- parseQueryString } = require("./request-utils");
-const Actions = require("./actions/index");
-
-/**
- * Functions handling the custom request view.
- */
-function CustomRequestView() {
- dumpn("CustomRequestView was instantiated");
-}
-
-CustomRequestView.prototype = {
- /**
- * Initialization function, called when the network monitor is started.
- */
- initialize: function () {
- dumpn("Initializing the CustomRequestView");
-
- this.updateCustomRequestEvent = getKeyWithEvent(this.onUpdate.bind(this));
- $("#custom-pane").addEventListener("input",
- this.updateCustomRequestEvent);
- },
-
- /**
- * Destruction function, called when the network monitor is closed.
- */
- destroy: function () {
- dumpn("Destroying the CustomRequestView");
-
- $("#custom-pane").removeEventListener("input",
- this.updateCustomRequestEvent);
- },
-
- /**
- * Populates this view with the specified data.
- *
- * @param object data
- * The data source (this should be the attachment of a request item).
- * @return object
- * Returns a promise that resolves upon population the view.
- */
- populate: Task.async(function* (data) {
- $("#custom-url-value").value = data.url;
- $("#custom-method-value").value = data.method;
- this.updateCustomQuery(data.url);
-
- if (data.requestHeaders) {
- let headers = data.requestHeaders.headers;
- $("#custom-headers-value").value = writeHeaderText(headers);
- }
- if (data.requestPostData) {
- let postData = data.requestPostData.postData.text;
- $("#custom-postdata-value").value = yield gNetwork.getString(postData);
- }
-
- window.emit(EVENTS.CUSTOMREQUESTVIEW_POPULATED);
- }),
-
- /**
- * Handle user input in the custom request form.
- *
- * @param object field
- * the field that the user updated.
- */
- onUpdate: function (field) {
- let selectedItem = NetMonitorView.RequestsMenu.selectedItem;
- let store = NetMonitorView.RequestsMenu.store;
- let value;
-
- switch (field) {
- case "method":
- value = $("#custom-method-value").value.trim();
- store.dispatch(Actions.updateRequest(selectedItem.id, { method: value }));
- break;
- case "url":
- value = $("#custom-url-value").value;
- this.updateCustomQuery(value);
- store.dispatch(Actions.updateRequest(selectedItem.id, { url: value }));
- break;
- case "query":
- let query = $("#custom-query-value").value;
- this.updateCustomUrl(query);
- value = $("#custom-url-value").value;
- store.dispatch(Actions.updateRequest(selectedItem.id, { url: value }));
- break;
- case "body":
- value = $("#custom-postdata-value").value;
- store.dispatch(Actions.updateRequest(selectedItem.id, {
- requestPostData: {
- postData: { text: value }
- }
- }));
- break;
- case "headers":
- let headersText = $("#custom-headers-value").value;
- value = parseHeadersText(headersText);
- store.dispatch(Actions.updateRequest(selectedItem.id, {
- requestHeaders: { headers: value }
- }));
- break;
- }
- },
-
- /**
- * Update the query string field based on the url.
- *
- * @param object url
- * The URL to extract query string from.
- */
- updateCustomQuery: function (url) {
- const paramsArray = parseQueryString(getUrlQuery(url));
-
- if (!paramsArray) {
- $("#custom-query").hidden = true;
- return;
- }
-
- $("#custom-query").hidden = false;
- $("#custom-query-value").value = writeQueryText(paramsArray);
- },
-
- /**
- * Update the url based on the query string field.
- *
- * @param object queryText
- * The contents of the query string field.
- */
- updateCustomUrl: function (queryText) {
- let params = parseQueryText(queryText);
- let queryString = writeQueryString(params);
-
- let url = $("#custom-url-value").value;
- let oldQuery = getUrlQuery(url);
- let path = url.replace(oldQuery, queryString);
-
- $("#custom-url-value").value = path;
- }
-};
-
-/**
- * Parse text representation of multiple HTTP headers.
- *
- * @param string text
- * Text of headers
- * @return array
- * Array of headers info {name, value}
- */
-function parseHeadersText(text) {
- return parseRequestText(text, "\\S+?", ":");
-}
-
-/**
- * Parse readable text list of a query string.
- *
- * @param string text
- * Text of query string representation
- * @return array
- * Array of query params {name, value}
- */
-function parseQueryText(text) {
- return parseRequestText(text, ".+?", "=");
-}
-
-/**
- * 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}
- */
-function parseRequestText(text, namereg, divider) {
- let regex = new RegExp("(" + namereg + ")\\" + divider + "\\s*(.+)");
- let pairs = [];
-
- for (let line of text.split("\n")) {
- let matches;
- if (matches = regex.exec(line)) { // eslint-disable-line
- let [, name, value] = matches;
- pairs.push({name: name, value: value});
- }
- }
- return pairs;
-}
-
-/**
- * Write out a list of query params into a chunk of text
- *
- * @param array params
- * Array of query params {name, value}
- * @return string
- * List of query params in text format
- */
-function writeQueryText(params) {
- return params.map(({name, value}) => name + "=" + value).join("\n");
-}
-
-/**
- * Write out a list of query params into a query string
- *
- * @param array params
- * Array of query params {name, value}
- * @return string
- * Query string that can be appended to a url.
- */
-function writeQueryString(params) {
- return params.map(({name, value}) => name + "=" + value).join("&");
-}
-
-exports.CustomRequestView = CustomRequestView;
--- a/devtools/client/netmonitor/events.js
+++ b/devtools/client/netmonitor/events.js
@@ -57,19 +57,16 @@ const EVENTS = {
// When the image response thumbnail is displayed in the UI.
RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
"NetMonitor:ResponseImageThumbnailAvailable",
// Fired when Sidebar has finished being populated.
SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
- // Fired when CustomRequestView has finished being populated.
- CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated",
-
// Fired when charts have been displayed in the PerformanceStatisticsView.
PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed",
// Fired once the NetMonitorController establishes a connection to the debug
// target.
CONNECTED: "connected",
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -10,17 +10,16 @@ DIRS += [
'reducers',
'selectors',
'shared',
'utils',
]
DevToolsModules(
'constants.js',
- 'custom-request-view.js',
'events.js',
'filter-predicates.js',
'l10n.js',
'netmonitor-controller.js',
'netmonitor-view.js',
'panel.js',
'prefs.js',
'request-list-context-menu.js',
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -6,26 +6,28 @@
/* globals $, gStore, NetMonitorController, dumpn */
"use strict";
const { testing: isTesting } = require("devtools/shared/flags");
const { Task } = require("devtools/shared/task");
const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");
const { RequestsMenuView } = require("./requests-menu-view");
-const { CustomRequestView } = require("./custom-request-view");
const { ToolbarView } = require("./toolbar-view");
const { SidebarView } = require("./sidebar-view");
const { StatisticsView } = require("./statistics-view");
const { ACTIVITY_TYPE } = require("./constants");
const { Prefs } = require("./prefs");
const { createFactory } = require("devtools/client/shared/vendor/react");
const Actions = require("./actions/index");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
+
+// components
+const CustomRequestPanel = createFactory(require("./shared/components/custom-request-panel"));
const DetailsPanel = createFactory(require("./shared/components/details-panel"));
// ms
const WDA_DEFAULT_VERIFY_INTERVAL = 50;
// Use longer timeout during testing as the tests need this process to succeed
// and two seconds is quite short on slow debug builds. The timeout here should
// be at least equal to the general mochitest timeout of 45 seconds so that this
@@ -40,23 +42,28 @@ var NetMonitorView = {
/**
* Initializes the network monitor view.
*/
initialize: function () {
this._initializePanes();
this.Toolbar.initialize(gStore);
this.RequestsMenu.initialize(gStore);
- this.CustomRequest.initialize();
this.Statistics.initialize(gStore);
+ this.customRequestPanel = $("#react-custom-request-panel-hook");
this.detailsPanel = $("#react-details-panel-hook");
ReactDOM.render(Provider(
{ store: gStore },
+ CustomRequestPanel(),
+ ), this.customRequestPanel);
+
+ ReactDOM.render(Provider(
+ { store: gStore },
DetailsPanel({ toolbox: NetMonitorController._toolbox }),
), this.detailsPanel);
// Store watcher here is for observing the statisticsOpen state change.
// It should be removed once we migrate to react and apply react/redex binding.
this.unsubscribeStore = gStore.subscribe(storeWatcher(
false,
() => gStore.getState().ui.statisticsOpen,
@@ -66,18 +73,18 @@ var NetMonitorView = {
/**
* Destroys the network monitor view.
*/
destroy: function () {
this._isDestroyed = true;
this.Toolbar.destroy();
this.RequestsMenu.destroy();
- this.CustomRequest.destroy();
this.Statistics.destroy();
+ ReactDOM.unmountComponentAtNode(this.customRequestPanel);
ReactDOM.unmountComponentAtNode(this.detailsPanel);
this.unsubscribeStore();
this._destroyPanes();
},
/**
* Initializes the UI for all the displayed panes.
@@ -263,12 +270,11 @@ function storeWatcher(initialValue, redu
}
/**
* Preliminary setup for the NetMonitorView object.
*/
NetMonitorView.Toolbar = new ToolbarView();
NetMonitorView.Sidebar = new SidebarView();
NetMonitorView.RequestsMenu = new RequestsMenuView();
-NetMonitorView.CustomRequest = new CustomRequestView();
NetMonitorView.Statistics = new StatisticsView();
exports.NetMonitorView = NetMonitorView;
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -30,75 +30,19 @@
class="devtools-main-content">
</html:div>
<splitter id="network-inspector-view-splitter"
class="devtools-side-splitter"/>
<deck id="details-pane"
hidden="true">
- <vbox id="custom-pane"
- class="tabpanel-content">
- <hbox align="baseline">
- <label data-localization="content=netmonitor.custom.newRequest"
- class="plain tabpanel-summary-label
- custom-header"/>
- <hbox flex="1" pack="end"
- class="devtools-toolbarbutton-group">
- <button id="custom-request-send-button"
- class="devtools-toolbarbutton"
- data-localization="label=netmonitor.custom.send"/>
- <button id="custom-request-close-button"
- class="devtools-toolbarbutton"
- data-localization="label=netmonitor.custom.cancel"/>
- </hbox>
- </hbox>
- <hbox id="custom-method-and-url"
- class="tabpanel-summary-container"
- align="center">
- <textbox id="custom-method-value"
- data-key="method"/>
- <textbox id="custom-url-value"
- flex="1"
- data-key="url"/>
- </hbox>
- <vbox id="custom-query"
- class="tabpanel-summary-container custom-section">
- <label class="plain tabpanel-summary-label"
- data-localization="content=netmonitor.custom.query"/>
- <textbox id="custom-query-value"
- class="tabpanel-summary-input"
- multiline="true"
- rows="4"
- wrap="off"
- data-key="query"/>
- </vbox>
- <vbox id="custom-headers"
- class="tabpanel-summary-container custom-section">
- <label class="plain tabpanel-summary-label"
- data-localization="content=netmonitor.custom.headers"/>
- <textbox id="custom-headers-value"
- class="tabpanel-summary-input"
- multiline="true"
- rows="8"
- wrap="off"
- data-key="headers"/>
- </vbox>
- <vbox id="custom-postdata"
- class="tabpanel-summary-container custom-section">
- <label class="plain tabpanel-summary-label"
- data-localization="content=netmonitor.custom.postData"/>
- <textbox id="custom-postdata-value"
- class="tabpanel-summary-input"
- multiline="true"
- rows="6"
- wrap="off"
- data-key="body"/>
- </vbox>
- </vbox>
+ <html:div xmlns="http://www.w3.org/1999/xhtml"
+ id="react-custom-request-panel-hook"
+ class="tabpanel-content"/>
<html:div xmlns="http://www.w3.org/1999/xhtml"
id="react-details-panel-hook"/>
</deck>
</hbox>
</vbox>
<html:div id="network-statistics-view">
--- a/devtools/client/netmonitor/reducers/requests.js
+++ b/devtools/client/netmonitor/reducers/requests.js
@@ -1,25 +1,31 @@
/* 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 I = require("devtools/client/shared/vendor/immutable");
-const { getUrlDetails } = require("../request-utils");
+const {
+ getUrlDetails,
+ getUrlQuery,
+} = require("../request-utils");
const {
ADD_REQUEST,
- UPDATE_REQUEST,
CLEAR_REQUESTS,
+ CLONE_SELECTED_REQUEST,
+ OPEN_SIDEBAR,
+ REMOVE_SELECTED_CUSTOM_REQUEST,
SELECT_REQUEST,
- PRESELECT_REQUEST,
- CLONE_SELECTED_REQUEST,
- REMOVE_SELECTED_CUSTOM_REQUEST,
- OPEN_SIDEBAR,
+ SEND_CUSTOM_REQUEST,
+ UPDATE_REQUEST,
+ UPDATE_SELECTED_REQUEST,
+ UPDATE_SELECTED_REQUEST_HEADER,
+ UPDATE_SELECTED_REQUEST_QUERY,
} = require("../constants");
const Request = I.Record({
id: null,
// Set to true in case of a request that's being edited as part of "edit and resend"
isCustom: false,
// Request properties - at the beginning, they are unknown and are gradually filled in
startedMillis: undefined,
@@ -38,16 +44,18 @@ const Request = I.Record({
securityState: undefined,
securityInfo: undefined,
mimeType: "text/plain",
contentSize: undefined,
transferredSize: undefined,
totalTime: undefined,
eventTimings: undefined,
headersSize: undefined,
+ // this state only appears when user edit the custom requst form
+ customQueryValue: undefined,
requestHeaders: undefined,
requestHeadersFromUploadStream: undefined,
requestCookies: undefined,
requestPostData: undefined,
responseHeaders: undefined,
responseCookies: undefined,
responseContent: undefined,
responseContentDataUri: undefined,
@@ -76,27 +84,116 @@ const UPDATE_PROPS = [
"securityState",
"securityInfo",
"mimeType",
"contentSize",
"transferredSize",
"totalTime",
"eventTimings",
"headersSize",
+ "customQueryValue",
"requestHeaders",
"requestHeadersFromUploadStream",
"requestCookies",
"requestPostData",
"responseHeaders",
"responseCookies",
"responseContent",
"responseContentDataUri",
"formDataSections",
];
+function updateRequest(state, action) {
+ let { requests, lastEndedMillis } = state;
+
+ let updatedRequest = requests.get(action.id);
+ if (!updatedRequest) {
+ return state;
+ }
+
+ updatedRequest = updatedRequest.withMutations(request => {
+ for (let [key, value] of Object.entries(action.data)) {
+ if (!UPDATE_PROPS.includes(key)) {
+ continue;
+ }
+
+ request[key] = value;
+
+ switch (key) {
+ case "url":
+ // Compute the additional URL details
+ request.urlDetails = getUrlDetails(value);
+ break;
+ case "totalTime":
+ const endedMillis = request.startedMillis + value;
+ lastEndedMillis = Math.max(lastEndedMillis, endedMillis);
+ break;
+ case "requestPostData":
+ request.requestHeadersFromUploadStream = {
+ headers: [],
+ headersSize: 0,
+ };
+ break;
+ }
+ }
+ });
+
+ return state.withMutations(st => {
+ st.requests = requests.set(updatedRequest.id, updatedRequest);
+ st.lastEndedMillis = lastEndedMillis;
+ });
+}
+
+/**
+ * Remove the currently selected custom request.
+ */
+function closeCustomRequest(state) {
+ let { requests, selectedId } = state;
+
+ if (!selectedId) {
+ return state;
+ }
+
+ let removedRequest = requests.get(selectedId);
+
+ // Only custom requests can be removed
+ if (!removedRequest || !removedRequest.isCustom) {
+ return state;
+ }
+
+ return state.withMutations(st => {
+ st.requests = requests.delete(removedRequest);
+ st.selectedId = null;
+ });
+}
+
+/**
+ * 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}
+ */
+function parseRequestText(text, namereg, divider) {
+ let regex = new RegExp(`(${namereg})\\${divider}\\s*(.+)`);
+ let pairs = [];
+
+ for (let line of text.split("\n")) {
+ let matches = regex.exec(line);
+ if (matches) {
+ let [, name, value] = matches;
+ pairs.push({
+ name,
+ value,
+ });
+ }
+ }
+ return pairs;
+}
+
function requestsReducer(state = new Requests(), action) {
switch (action.type) {
case ADD_REQUEST: {
return state.withMutations(st => {
let newRequest = new Request(Object.assign(
{ id: action.id },
action.data,
{ urlDetails: getUrlDetails(action.data.url) }
@@ -114,66 +211,107 @@ function requestsReducer(state = new Req
// Select the request if it was preselected and there is no other selection
if (st.preselectedId && st.preselectedId === action.id) {
st.selectedId = st.selectedId || st.preselectedId;
st.preselectedId = null;
}
});
}
+ case UPDATE_REQUEST: {
+ return updateRequest(state, action);
+ }
+ case UPDATE_SELECTED_REQUEST: {
+ let { selectedId } = state;
- case UPDATE_REQUEST: {
- let { requests, lastEndedMillis } = state;
+ if (!selectedId) {
+ return state;
+ }
- let updatedRequest = requests.get(action.id);
- if (!updatedRequest) {
+ action.id = selectedId;
+ return updateRequest(state, action);
+ }
+ case UPDATE_SELECTED_REQUEST_HEADER: {
+ let { requests, selectedId } = state;
+
+ if (!selectedId) {
return state;
}
- updatedRequest = updatedRequest.withMutations(request => {
- for (let [key, value] of Object.entries(action.data)) {
- if (!UPDATE_PROPS.includes(key)) {
- continue;
+ let request = requests.get(selectedId);
+ let customHeadersValue = action.data.requestHeaders.customHeadersValue;
+ // Parse text representation of multiple HTTP headers
+ let headersArray = parseRequestText(customHeadersValue, "\\S+?", ":");
+ // do not update headers while headers string is not parsable
+ if (headersArray.length != customHeadersValue.split("\n").length) {
+ let paramsAction = Object.assign({}, action, {
+ id: selectedId,
+ data: {
+ requestHeaders: {
+ customHeadersValue,
+ headers: request.requestHeaders.headers,
+ }
}
-
- request[key] = value;
+ });
+ return updateRequest(state, paramsAction);
+ }
- switch (key) {
- case "url":
- // Compute the additional URL details
- request.urlDetails = getUrlDetails(value);
- break;
- case "totalTime":
- const endedMillis = request.startedMillis + value;
- lastEndedMillis = Math.max(lastEndedMillis, endedMillis);
- break;
- case "requestPostData":
- request.requestHeadersFromUploadStream = {
- headers: [],
- headersSize: 0,
- };
- break;
+ let newAction = Object.assign({}, action, {
+ id: selectedId,
+ data: {
+ requestHeaders: {
+ customHeadersValue: null,
+ headers: headersArray,
}
}
});
+ return updateRequest(state, newAction);
+ }
+ case UPDATE_SELECTED_REQUEST_QUERY: {
+ let { requests, selectedId } = state;
- return state.withMutations(st => {
- st.requests = requests.set(updatedRequest.id, updatedRequest);
- st.lastEndedMillis = lastEndedMillis;
+ if (!selectedId) {
+ return state;
+ }
+
+ let customQueryValue = action.data.customQueryValue;
+ // Parse readable text list of a query string
+ let queryArray = customQueryValue ?
+ parseRequestText(customQueryValue, ".+?", "=") : [];
+ // do not update url while params string is not parsable
+ // update request.params to show current user inputs
+ if (!customQueryValue || queryArray.length != customQueryValue.split("\n").length) {
+ let queryAction = Object.assign({}, action, {
+ id: selectedId,
+ data: {
+ customQueryValue,
+ }
+ });
+ return updateRequest(state, queryAction);
+ }
+
+ let request = requests.get(selectedId);
+ // Write out a list of query params into a query string
+ let queryString = queryArray.map(({name, value}) => name + "=" + value).join("&");
+ let url = request.url.replace(getUrlQuery(request.url), queryString);
+ let newAction = Object.assign({}, action, {
+ id: selectedId,
+ data: {
+ customQueryValue: null,
+ url,
+ }
});
+ return updateRequest(state, newAction);
}
case CLEAR_REQUESTS: {
return new Requests();
}
case SELECT_REQUEST: {
return state.set("selectedId", action.id);
}
- case PRESELECT_REQUEST: {
- return state.set("preselectedId", action.id);
- }
case CLONE_SELECTED_REQUEST: {
let { requests, selectedId } = state;
if (!selectedId) {
return state;
}
let clonedRequest = requests.get(selectedId);
@@ -192,32 +330,24 @@ function requestsReducer(state = new Req
});
return state.withMutations(st => {
st.requests = requests.set(newRequest.id, newRequest);
st.selectedId = newRequest.id;
});
}
case REMOVE_SELECTED_CUSTOM_REQUEST: {
- let { requests, selectedId } = state;
-
- if (!selectedId) {
- return state;
- }
-
- // Only custom requests can be removed
- let removedRequest = requests.get(selectedId);
- if (!removedRequest || !removedRequest.isCustom) {
- return state;
- }
-
- return state.withMutations(st => {
- st.requests = requests.delete(selectedId);
- st.selectedId = null;
- });
+ return closeCustomRequest(state);
+ }
+ case SEND_CUSTOM_REQUEST: {
+ // When a new request with a given id is added in future, select it immediately.
+ // where we know in advance the ID of the request, at a time when it
+ // wasn't sent yet.
+ state.set("preselectedId", action.id);
+ return closeCustomRequest(state);
}
case OPEN_SIDEBAR: {
if (!action.open) {
return state.set("selectedId", null);
}
if (!state.selectedId && !state.requests.isEmpty()) {
return state.set("selectedId", state.requests.first().id);
--- a/devtools/client/netmonitor/request-utils.js
+++ b/devtools/client/netmonitor/request-utils.js
@@ -1,46 +1,19 @@
/* 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/. */
/* eslint-disable mozilla/reject-some-requires */
"use strict";
-const { KeyCodes } = require("devtools/client/shared/keycodes");
const { Task } = require("devtools/shared/task");
/**
- * Helper method to get a wrapped function which can be bound to as
- * an event listener directly and is executed only when data-key is
- * present in event.target.
- *
- * @param {function} callback - function to execute execute when data-key
- * is present in event.target.
- * @param {bool} onlySpaceOrReturn - flag to indicate if callback should only
- * be called when the space or return button
- * is pressed
- * @return {function} wrapped function with the target data-key as the first argument
- * and the event as the second argument.
- */
-function getKeyWithEvent(callback, onlySpaceOrReturn) {
- return function (event) {
- let key = event.target.getAttribute("data-key");
- let filterKeyboardEvent = !onlySpaceOrReturn ||
- event.keyCode === KeyCodes.DOM_VK_SPACE ||
- event.keyCode === KeyCodes.DOM_VK_RETURN;
-
- if (key && filterKeyboardEvent) {
- callback(key);
- }
- };
-}
-
-/**
* Extracts any urlencoded form data sections (e.g. "?foo=bar&baz=42") from a
* POST request.
*
* @param {object} headers - the "requestHeaders".
* @param {object} uploadHeaders - the "requestHeadersFromUploadStream".
* @param {object} postData - the "requestPostData".
* @param {function} getString - callback to retrieve a string from a LongStringGrip.
* @return {array} a promise list that is resolved with the extracted form data.
@@ -249,17 +222,16 @@ function parseQueryString(query) {
return {
name: param[0] ? decodeUnicodeUrl(param[0]) : "",
value: param[1] ? decodeUnicodeUrl(param[1]) : "",
};
});
}
module.exports = {
- getKeyWithEvent,
getFormDataSections,
fetchHeaders,
formDataURI,
writeHeaderText,
decodeUnicodeUrl,
getAbbreviatedMimeType,
getUrlBaseName,
getUrlQuery,
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -134,62 +134,41 @@ RequestsMenuView.prototype = {
{ formDataSections },
true,
));
});
}
},
));
- this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
- this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
-
this._summary = $("#requests-menu-network-summary-button");
this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
this.onResize = this.onResize.bind(this);
this._splitter = $("#network-inspector-view-splitter");
this._splitter.addEventListener("mouseup", this.onResize);
window.addEventListener("resize", this.onResize);
this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" });
this.mountPoint = $("#network-table");
ReactDOM.render(createElement(Provider,
{ store: this.store },
RequestList()
), this.mountPoint);
-
- window.once("connected", this._onConnect.bind(this));
- },
-
- _onConnect() {
- if (NetMonitorController.supportsCustomRequest) {
- $("#custom-request-send-button")
- .addEventListener("click", this.sendCustomRequestEvent);
- $("#custom-request-close-button")
- .addEventListener("click", this.closeCustomRequestEvent);
- }
},
/**
* Destruction function, called when the network monitor is closed.
*/
destroy() {
dumpn("Destroying the RequestsMenuView");
Prefs.filters = getActiveFilters(this.store.getState());
- // this.flushRequestsTask.disarm();
-
- $("#custom-request-send-button")
- .removeEventListener("click", this.sendCustomRequestEvent);
- $("#custom-request-close-button")
- .removeEventListener("click", this.closeCustomRequestEvent);
-
this._splitter.removeEventListener("mouseup", this.onResize);
window.removeEventListener("resize", this.onResize);
this.tooltip.destroy();
ReactDOM.unmountComponentAtNode(this.mountPoint);
},
@@ -421,53 +400,12 @@ RequestsMenuView.prototype = {
// Allow requests to settle down first.
setNamedTimeout("resize-events", RESIZE_REFRESH_RATE, () => {
const waterfallHeaderEl = $("#requests-menu-waterfall-header-box");
if (waterfallHeaderEl) {
const { width } = waterfallHeaderEl.getBoundingClientRect();
this.store.dispatch(Actions.resizeWaterfall(width));
}
});
- },
-
- /**
- * Create a new custom request form populated with the data from
- * the currently selected request.
- */
- cloneSelectedRequest() {
- this.store.dispatch(Actions.cloneSelectedRequest());
- },
-
- /**
- * Send a new HTTP request using the data in the custom request form.
- */
- sendCustomRequest: function () {
- let selected = getSelectedRequest(this.store.getState());
-
- let data = {
- url: selected.url,
- method: selected.method,
- httpVersion: selected.httpVersion,
- };
- if (selected.requestHeaders) {
- data.headers = selected.requestHeaders.headers;
- }
- if (selected.requestPostData) {
- data.body = selected.requestPostData.postData.text;
- }
-
- NetMonitorController.webConsoleClient.sendHTTPRequest(data, response => {
- let id = response.eventActor.actor;
- this.store.dispatch(Actions.preselectRequest(id));
- });
-
- this.closeCustomRequest();
- },
-
- /**
- * Remove the currently selected custom request.
- */
- closeCustomRequest() {
- this.store.dispatch(Actions.removeSelectedCustomRequest());
- },
+ }
};
exports.RequestsMenuView = RequestsMenuView;
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/shared/components/custom-request-panel.js
@@ -0,0 +1,215 @@
+/* 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 {
+ DOM,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { L10N } = require("../../l10n");
+const Actions = require("../../actions/index");
+const { getSelectedRequest } = require("../../selectors/index");
+const {
+ getUrlQuery,
+ parseQueryString,
+ writeHeaderText,
+} = require("../../request-utils");
+
+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_POSTDATA = L10N.getStr("netmonitor.custom.postData");
+const CUSTOM_QUERY = L10N.getStr("netmonitor.custom.query");
+const CUSTOM_SEND = L10N.getStr("netmonitor.custom.send");
+
+function CustomRequestPanel({
+ removeSelectedCustomRequest,
+ request,
+ sendCustomRequest,
+ updateBody,
+ updateHeaders,
+ updateMethod,
+ updateQuery,
+ updateUrl,
+}) {
+ let method = null;
+ let customQueryValue = null;
+ let requestHeaders = null;
+ let requestPostData = null;
+ let url = null;
+
+ if (request) {
+ method = request.method || request.method;
+ customQueryValue = request.customQueryValue || customQueryValue;
+ requestHeaders = request.requestHeaders || requestHeaders;
+ requestPostData = request.requestPostData || requestPostData;
+ url = request.url || url;
+ }
+
+ let headers = "";
+ if (requestHeaders) {
+ headers = requestHeaders.customHeadersValue ?
+ requestHeaders.customHeadersValue : writeHeaderText(requestHeaders.headers);
+ }
+ let queryArray = url ? parseQueryString(getUrlQuery(url)) : [];
+ let params = customQueryValue ? customQueryValue : writeQueryText(queryArray);
+ let postData = requestPostData && requestPostData.postData.text ?
+ requestPostData.postData.text : "";
+
+ return div({ className: "custom-request-panel" },
+ div({ className: "tabpanel-summary-container custom-request" },
+ label({ className: "tabpanel-summary-label custom-header" },
+ CUSTOM_NEW_REQUEST
+ ),
+ button({
+ className: "devtools-toolbarbutton, tool-button",
+ id: "custom-request-send-button",
+ onClick: sendCustomRequest,
+ },
+ CUSTOM_SEND
+ ),
+ button({
+ className: "devtools-toolbarbutton, tool-button",
+ id: "custom-request-close-button",
+ onClick: removeSelectedCustomRequest,
+ },
+ CUSTOM_CANCEL
+ ),
+ ),
+ div({
+ className: "tabpanel-summary-container custom-method-and-url",
+ id: "custom-method-and-url",
+ },
+ input({
+ className: "custom-method-value",
+ id: "custom-method-value",
+ onChange: updateMethod,
+ value: method || "GET",
+ }),
+ input({
+ className: "custom-url-value",
+ id: "custom-url-value",
+ onChange: updateUrl,
+ value: url || "http://",
+ }),
+ ),
+ // hide query field when there is no params
+ params ? div({
+ className: "tabpanel-summary-container custom-section",
+ id: "custom-query",
+ },
+ label({ className: "tabpanel-summary-label" }, CUSTOM_QUERY),
+ textarea({
+ className: "tabpanel-summary-input",
+ id: "custom-query-value",
+ onChange: updateQuery,
+ rows: 4,
+ value: params,
+ wrap: "off",
+ })
+ ) : null,
+ div({
+ id: "custom-headers",
+ className: "tabpanel-summary-container custom-section",
+ },
+ label({ className: "tabpanel-summary-label" }, CUSTOM_HEADERS),
+ textarea({
+ className: "tabpanel-summary-input",
+ id: "custom-headers-value",
+ onChange: updateHeaders,
+ rows: 8,
+ value: headers,
+ wrap: "off",
+ })
+ ),
+ div({
+ id: "custom-postdata",
+ className: "tabpanel-summary-container custom-section",
+ },
+ label({ className: "tabpanel-summary-label" }, CUSTOM_POSTDATA),
+ textarea({
+ className: "tabpanel-summary-input",
+ id: "custom-postdata-value",
+ onChange: updateBody,
+ rows: 6,
+ value: postData,
+ wrap: "off",
+ })
+ ),
+ );
+}
+
+CustomRequestPanel.displayName = "CustomRequestPanel";
+
+CustomRequestPanel.propTypes = {
+ request: PropTypes.object,
+ removeSelectedCustomRequest: PropTypes.func,
+ sendCustomRequest: PropTypes.func,
+ updateBody: PropTypes.func,
+ updateHeaders: PropTypes.func,
+ updateMethod: PropTypes.func,
+ updateQuery: PropTypes.func,
+ updateUrl: PropTypes.func,
+};
+
+/**
+ * Write out a list of query params into a chunk of text
+ *
+ * @param {array} params
+ * Array of query params {name, value}
+ * @return {string}
+ * List of query params in text format
+ */
+function writeQueryText(params) {
+ return params ?
+ params.map(({name, value}) => name + "=" + value).join("\n") : "";
+}
+
+module.exports = connect(
+ state => {
+ const request = getSelectedRequest(state);
+ return { request };
+ },
+ dispatch => ({
+ removeSelectedCustomRequest: () => dispatch(Actions.removeSelectedCustomRequest()),
+ sendCustomRequest: () => dispatch(Actions.sendCustomRequest()),
+ updateBody: (evt) => {
+ let text = evt.target.value;
+ dispatch(Actions.updateSelectedRequest({
+ requestPostData: {
+ postData: { text }
+ }
+ }));
+ },
+ updateHeaders: (evt) => {
+ let customHeadersValue = evt.target.value;
+ dispatch(Actions.updateSelectedRequestHeader({
+ requestHeaders: { customHeadersValue }
+ }));
+ },
+ updateMethod: (evt) => {
+ let method = evt.target.value.trim();
+ dispatch(Actions.updateSelectedRequest({ method }));
+ },
+ // Update the url based on the query string field
+ updateQuery: (evt) => {
+ let customQueryValue = evt.target.value;
+ dispatch(Actions.updateSelectedRequestQuery({ customQueryValue }));
+ },
+ updateUrl: (evt) => {
+ let url = evt.target.value;
+ dispatch(Actions.updateSelectedRequest({ url }));
+ },
+ })
+)(CustomRequestPanel);
--- a/devtools/client/netmonitor/shared/components/moz.build
+++ b/devtools/client/netmonitor/shared/components/moz.build
@@ -1,14 +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/.
DevToolsModules(
'cookies-panel.js',
+ 'custom-request-panel.js',
'details-panel.js',
'editor.js',
'headers-panel.js',
'params-panel.js',
'preview-panel.js',
'properties-view.js',
'response-panel.js',
'security-panel.js',
--- a/devtools/client/netmonitor/sidebar-view.js
+++ b/devtools/client/netmonitor/sidebar-view.js
@@ -33,20 +33,16 @@ SidebarView.prototype = {
* @param object data
* The data source (this should be the attachment of a request item).
* @return object
* Returns a promise that resolves upon population of the subview.
*/
populate: Task.async(function* (data) {
let isCustom = data.isCustom;
- if (isCustom) {
- yield NetMonitorView.CustomRequest.populate(data);
- }
-
$("#details-pane").selectedIndex = isCustom ? 0 : 1;
window.emit(EVENTS.SIDEBAR_POPULATED);
})
};
exports.SidebarView = SidebarView;
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -125,16 +125,17 @@ skip-if = (os == 'linux' && debug && bit
[browser_net_prefs-and-l10n.js]
[browser_net_prefs-reload.js]
[browser_net_raw_headers.js]
[browser_net_reload-button.js]
[browser_net_reload-markers.js]
[browser_net_req-resp-bodies.js]
[browser_net_resend_cors.js]
[browser_net_resend_headers.js]
+[browser_net_resend.js]
[browser_net_security-details.js]
[browser_net_security-error.js]
[browser_net_security-icon-click.js]
[browser_net_security-redirect.js]
[browser_net_security-state.js]
[browser_net_security-tab-deselect.js]
[browser_net_security-tab-visibility.js]
[browser_net_security-warnings.js]
--- a/devtools/client/netmonitor/test/browser_net_resend.js
+++ b/devtools/client/netmonitor/test/browser_net_resend.js
@@ -12,50 +12,49 @@ const ADD_HEADER = "Test-header: true";
const ADD_UA_HEADER = "User-Agent: Custom-Agent";
const ADD_POSTDATA = "&t3=t4";
add_task(function* () {
let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
info("Starting test... ");
let { panelWin } = monitor;
- let { document, EVENTS, NetMonitorView } = panelWin;
+ let { document, gStore, EVENTS, NetMonitorView, windowRequire } = panelWin;
+ let Actions = windowRequire("devtools/client/netmonitor/actions/index");
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let wait = waitForNetworkEvents(monitor, 0, 2);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
});
yield wait;
let origItem = RequestsMenu.getItemAtIndex(0);
RequestsMenu.selectedItem = origItem;
// add a new custom request cloned from selected request
- let onPopulated = panelWin.once(EVENTS.CUSTOMREQUESTVIEW_POPULATED);
- RequestsMenu.cloneSelectedRequest();
- yield onPopulated;
+ gStore.dispatch(Actions.cloneSelectedRequest());
testCustomForm(origItem);
let customItem = RequestsMenu.selectedItem;
testCustomItem(customItem, origItem);
// edit the custom request
yield editCustomForm();
// FIXME: reread the customItem, it's been replaced by a new object (immutable!)
customItem = RequestsMenu.selectedItem;
testCustomItemChanged(customItem, origItem);
// send the new request
wait = waitForNetworkEvents(monitor, 0, 1);
- RequestsMenu.sendCustomRequest();
+ gStore.dispatch(Actions.sendCustomRequest());
yield wait;
let sentItem = RequestsMenu.selectedItem;
testSentRequest(sentItem, origItem);
return teardown(monitor);
function testCustomItem(item, orig) {
--- a/devtools/client/netmonitor/test/browser_net_resend_cors.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_cors.js
@@ -7,17 +7,18 @@
* Tests if resending a CORS request avoids the security checks and doesn't send
* a preflight OPTIONS request (bug 1270096 and friends)
*/
add_task(function* () {
let { tab, monitor } = yield initNetMonitor(CORS_URL);
info("Starting test... ");
- let { EVENTS, NetMonitorView } = monitor.panelWin;
+ let { EVENTS, gStore, NetMonitorView, windowRequire } = monitor.panelWin;
+ let Actions = windowRequire("devtools/client/netmonitor/actions/index");
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let requestUrl = "http://test1.example.com" + CORS_SJS_PATH;
info("Waiting for OPTIONS, then POST");
let wait = waitForNetworkEvents(monitor, 1, 1);
@@ -40,22 +41,20 @@ add_task(function* () {
let onRequests = waitForNetworkEvents(monitor, 1, 1);
for (let [i, method] of METHODS.entries()) {
let item = RequestsMenu.getItemAtIndex(i);
info(`Selecting the ${method} request (at index ${i})`);
RequestsMenu.selectedItem = item;
info("Cloning the selected request into a custom clone");
- let onPopulate = monitor.panelWin.once(EVENTS.CUSTOMREQUESTVIEW_POPULATED);
- RequestsMenu.cloneSelectedRequest();
- yield onPopulate;
+ gStore.dispatch(Actions.cloneSelectedRequest());
info("Sending the cloned request (without change)");
- RequestsMenu.sendCustomRequest();
+ gStore.dispatch(Actions.sendCustomRequest());
}
info("Waiting for both resent requests");
yield onRequests;
// Check the resent requests
for (let [i, method] of METHODS.entries()) {
let index = i + 2;
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -40,28 +40,24 @@
#details-pane.pane-collapsed {
visibility: hidden;
}
#details-pane-toggle[disabled] {
display: none;
}
-#custom-pane {
+#react-custom-request-panel-hook {
overflow: auto;
}
#response-content-image-box {
overflow: auto;
}
-#network-statistics-charts {
- overflow: auto;
-}
-
.cropped-textbox .textbox-input {
/* workaround for textbox not supporting the @crop attribute */
text-overflow: ellipsis;
}
:root.theme-dark {
--table-splitter-color: rgba(255,255,255,0.15);
--table-zebra-background: rgba(255,255,255,0.05);
@@ -787,34 +783,53 @@
}
@media (min-resolution: 1.1dppx) {
.security-warning-icon {
background-image: url(images/alerticon-warning@2x.png);
}
}
-/* Custom request form */
+/* Custom request view */
+
+#react-custom-request-panel-hook {
+ padding: 0.6em 1em;
+}
-#custom-pane {
- padding: 0.6em 0.5em;
+.custom-request-panel {
+ height: 100vh;
+}
+
+.custom-header,
+.custom-method-and-url,
+.custom-request,
+.custom-section {
+ display: flex;
}
.custom-header {
+ flex-grow: 1;
font-size: 1.1em;
}
.custom-section {
+ flex-direction: column;
margin-top: 0.5em;
}
-#custom-method-value {
+
+.custom-method-value {
width: 4.5em;
}
+.custom-url-value {
+ flex-grow: 1;
+ margin-inline-start: 6px;
+}
+
/* Performance analysis buttons */
#requests-menu-network-summary-button {
display: flex;
flex-wrap: nowrap;
align-items: center;
background: none;
box-shadow: none;
@@ -841,17 +856,17 @@
#requests-menu-network-summary-button:hover > .summary-info-icon,
#requests-menu-network-summary-button:hover > .summary-info-text {
opacity: 1;
}
/* Performance analysis view */
#network-statistics-view {
- display: -moz-box;
+ display: flex;
}
#network-statistics-toolbar {
border: none;
margin: 0;
padding: 0;
}
@@ -869,20 +884,22 @@
#network-statistics-view-splitter {
border-color: rgba(0,0,0,0.2);
cursor: default;
pointer-events: none;
}
#network-statistics-charts {
min-height: 1px;
-}
-
-#network-statistics-charts {
background-color: var(--theme-sidebar-background);
+ overflow: auto;
+ display: flex;
+ flex-grow: 1;
+ align-items: center;
+ justify-content: center;
}
#network-statistics-charts .pie-chart-container {
margin-inline-start: 3vw;
margin-inline-end: 1vw;
}
#network-statistics-charts .table-chart-container {
@@ -1206,45 +1223,45 @@
}
.headers-summary,
.response-summary {
display: flex;
align-items: center;
}
-.headers-summary .tool-button {
+.tool-button {
border: 1px solid transparent;
color: var(--theme-body-color);
transition: background 0.05s ease-in-out;
margin-inline-end: 6px;
padding: 0 5px;
}
-.theme-light .headers-summary .tool-button {
+.theme-light .tool-button {
background-color: var(--toolbar-tab-hover);
}
-.theme-light .headers-summary .tool-button:hover {
+.theme-light .tool-button:hover {
background-color: rgba(170, 170, 170, 0.3);
}
-.theme-light .headers-summary .tool-button:hover:active {
+.theme-light .tool-button:hover:active {
background-color: var(--toolbar-tab-hover-active);
}
-.theme-dark .headers-summary .tool-button {
+.theme-dark .tool-button {
background-color: rgba(0, 0, 0, 0.2);
}
-.theme-dark .headers-summary .tool-button:hover {
+.theme-dark .tool-button:hover {
background-color: rgba(0, 0, 0, 0.3);
}
-.theme-dark .headers-summary .tool-button:hover:active {
+.theme-dark .tool-button:hover:active {
background-color: rgba(0, 0, 0, 0.4);
}
.headers-summary .requests-menu-status-icon {
min-width: 10px;
}
.headers-summary .raw-headers-container {
@@ -1285,20 +1302,13 @@
width: 100%;
height: 100%;
}
/*
* FIXME: normal html block element cannot fill outer XUL element
* This workaround should be removed after netmonitor is migrated to react
*/
-
+#react-custom-request-panel-hook,
#react-details-panel-hook {
display: flex;
flex-direction: column;
}
-
-#network-statistics-charts,
-#primed-cache-chart,
-#empty-cache-chart {
- display: -moz-box;
- -moz-box-flex: 1;
-}