--- a/devtools/client/netmonitor/actions/requests.js
+++ b/devtools/client/netmonitor/actions/requests.js
@@ -1,21 +1,25 @@
/* 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 {
ADD_REQUEST,
- UPDATE_REQUEST,
+ CLEAR_REQUESTS,
CLONE_SELECTED_REQUEST,
REMOVE_SELECTED_CUSTOM_REQUEST,
- CLEAR_REQUESTS,
+ SEND_CUSTOM_REQUEST,
+ UPDATE_REQUEST,
} = require("../constants");
+const { getSelectedRequest } = require("../selectors/index");
function addRequest(id, data, batch) {
return {
type: ADD_REQUEST,
id,
data,
meta: { batch },
};
@@ -36,16 +40,53 @@ function updateRequest(id, data, batch)
*/
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 selected = getSelectedRequest(getState());
+
+ if (!selected) {
+ return;
+ }
+
+ // 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) => {
+ return dispatch({
+ 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 +94,14 @@ function removeSelectedCustomRequest() {
function clearRequests() {
return {
type: CLEAR_REQUESTS
};
}
module.exports = {
addRequest,
- updateRequest,
+ clearRequests,
cloneSelectedRequest,
removeSelectedCustomRequest,
- clearRequests,
+ sendCustomRequest,
+ updateRequest,
};
--- 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,20 +16,20 @@ 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",
WATERFALL_RESIZE: "WATERFALL_RESIZE",
};
// Descriptions for what this frontend is currently doing.
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
@@ -4,40 +4,47 @@
/* globals $, gStore, NetMonitorController, dumpn */
"use strict";
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 { SidebarView } = require("./sidebar-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"));
const StatisticsPanel = createFactory(require("./components/statistics-panel"));
const Toolbar = createFactory(require("./components/toolbar"));
/**
* Object defining the network monitor view components.
*/
var NetMonitorView = {
/**
* Initializes the network monitor view.
*/
initialize: function () {
this._initializePanes();
+ this.customRequestPanel = $("#react-custom-request-panel-hook");
+
+ ReactDOM.render(Provider(
+ { store: gStore },
+ CustomRequestPanel(),
+ ), this.customRequestPanel);
+
this.detailsPanel = $("#react-details-panel-hook");
ReactDOM.render(Provider(
{ store: gStore },
DetailsPanel({ toolbox: NetMonitorController._toolbox }),
), this.detailsPanel);
this.statisticsPanel = $("#statistics-panel");
@@ -50,34 +57,33 @@ var NetMonitorView = {
this.toolbar = $("#react-toolbar-hook");
ReactDOM.render(Provider(
{ store: gStore },
Toolbar(),
), this.toolbar);
this.RequestsMenu.initialize(gStore);
- this.CustomRequest.initialize();
// 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,
this.toggleFrontendMode.bind(this)
));
},
/**
* Destroys the network monitor view.
*/
destroy: function () {
this._isDestroyed = true;
this.RequestsMenu.destroy();
- this.CustomRequest.destroy();
+ ReactDOM.unmountComponentAtNode(this.customRequestPanel);
ReactDOM.unmountComponentAtNode(this.detailsPanel);
ReactDOM.unmountComponentAtNode(this.statisticsPanel);
ReactDOM.unmountComponentAtNode(this.toolbar);
this.unsubscribeStore();
this._destroyPanes();
},
@@ -190,11 +196,10 @@ function storeWatcher(initialValue, redu
};
}
/**
* Preliminary setup for the NetMonitorView object.
*/
NetMonitorView.Sidebar = new SidebarView();
NetMonitorView.RequestsMenu = new RequestsMenuView();
-NetMonitorView.CustomRequest = new CustomRequestView();
exports.NetMonitorView = NetMonitorView;
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -30,75 +30,18 @@
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"/>
<html:div xmlns="http://www.w3.org/1999/xhtml"
id="react-details-panel-hook"/>
</deck>
</hbox>
</vbox>
<html:div xmlns="http://www.w3.org/1999/xhtml"
--- a/devtools/client/netmonitor/reducers/requests.js
+++ b/devtools/client/netmonitor/reducers/requests.js
@@ -3,23 +3,23 @@
* 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 {
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,
} = 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 +38,19 @@ const Request = I.Record({
securityState: undefined,
securityInfo: undefined,
mimeType: "text/plain",
contentSize: undefined,
transferredSize: undefined,
totalTime: undefined,
eventTimings: undefined,
headersSize: undefined,
+ // Text value is used for storing custom request query
+ // which 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 +79,51 @@ const UPDATE_PROPS = [
"securityState",
"securityInfo",
"mimeType",
"contentSize",
"transferredSize",
"totalTime",
"eventTimings",
"headersSize",
+ "customQueryValue",
"requestHeaders",
"requestHeadersFromUploadStream",
"requestCookies",
"requestPostData",
"responseHeaders",
"responseCookies",
"responseContent",
"responseContentDataUri",
"formDataSections",
];
+/**
+ * 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 = st.requests.delete(selectedId);
+ st.selectedId = null;
+ });
+}
+
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,17 +141,16 @@ 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: {
let { requests, lastEndedMillis } = state;
let updatedRequest = requests.get(action.id);
if (!updatedRequest) {
return state;
}
@@ -161,19 +187,16 @@ function requestsReducer(state = new Req
});
}
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 +215,23 @@ 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.
+ return closeCustomRequest(state.set("preselectedId", action.id));
}
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;
--- a/devtools/client/netmonitor/selectors/requests.js
+++ b/devtools/client/netmonitor/selectors/requests.js
@@ -89,17 +89,17 @@ const getDisplayedRequestsSummary = crea
bytes: totalBytes,
millis: totalMillis,
};
}
);
const getSelectedRequest = createSelector(
state => state.requests,
- ({ selectedId, requests }) => selectedId ? requests.get(selectedId) : null
+ ({ selectedId, requests }) => selectedId ? requests.get(selectedId) : undefined
);
function getRequestById(state, id) {
return state.requests.requests.get(id);
}
function getDisplayedRequestById(state, id) {
return getDisplayedRequests(state).find(r => r.id === id);
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/shared/components/custom-request-panel.js
@@ -0,0 +1,257 @@
+/* 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,
+ 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,
+ updateRequest,
+}) {
+ let {
+ method,
+ customQueryValue,
+ requestHeaders,
+ requestPostData,
+ url,
+ } = request;
+
+ let headers = "";
+ if (requestHeaders) {
+ headers = requestHeaders.customHeadersValue ?
+ requestHeaders.customHeadersValue : writeHeaderText(requestHeaders.headers);
+ }
+ let queryArray = url ? parseQueryString(getUrlQuery(url)) : [];
+ let params = customQueryValue;
+ if (!params) {
+ params = queryArray ?
+ queryArray.map(({ name, value }) => name + "=" + value).join("\n") : "";
+ }
+ let postData = requestPostData && requestPostData.postData.text ?
+ requestPostData.postData.text : "";
+
+ return (
+ div({ className: "custom-request-panel" },
+ div({ className: "tabpanel-summary-container custom-request" },
+ div({ className: "custom-request-label custom-header" },
+ CUSTOM_NEW_REQUEST
+ ),
+ button({
+ className: "devtools-button",
+ id: "custom-request-send-button",
+ onClick: sendCustomRequest,
+ },
+ CUSTOM_SEND
+ ),
+ button({
+ className: "devtools-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: (evt) => updateCustomRequestFields(evt, request, updateRequest),
+ value: method || "GET",
+ }),
+ input({
+ className: "custom-url-value",
+ id: "custom-url-value",
+ onChange: (evt) => 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",
+ },
+ div({ className: "custom-request-label" }, CUSTOM_QUERY),
+ textarea({
+ className: "tabpanel-summary-input",
+ id: "custom-query-value",
+ onChange: (evt) => updateCustomRequestFields(evt, request, updateRequest),
+ rows: 4,
+ value: params,
+ wrap: "off",
+ })
+ ) : null,
+ div({
+ id: "custom-headers",
+ className: "tabpanel-summary-container custom-section",
+ },
+ div({ className: "custom-request-label" }, CUSTOM_HEADERS),
+ textarea({
+ className: "tabpanel-summary-input",
+ id: "custom-headers-value",
+ onChange: (evt) => updateCustomRequestFields(evt, request, updateRequest),
+ rows: 8,
+ value: headers,
+ wrap: "off",
+ })
+ ),
+ div({
+ id: "custom-postdata",
+ className: "tabpanel-summary-container custom-section",
+ },
+ div({ className: "custom-request-label" }, CUSTOM_POSTDATA),
+ textarea({
+ className: "tabpanel-summary-input",
+ id: "custom-postdata-value",
+ onChange: (evt) => updateCustomRequestFields(evt, request, updateRequest),
+ rows: 6,
+ value: postData,
+ wrap: "off",
+ })
+ ),
+ )
+ );
+}
+
+CustomRequestPanel.displayName = "CustomRequestPanel";
+
+CustomRequestPanel.propTypes = {
+ removeSelectedCustomRequest: PropTypes.func.isRequired,
+ request: PropTypes.object,
+ sendCustomRequest: PropTypes.func.isRequired,
+ updateRequest: PropTypes.func.isRequired,
+};
+
+/**
+ * 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;
+}
+
+/**
+ * Update Custom Request Fields
+ *
+ * @param {Object} evt click event
+ * @param {Object} request current request
+ * @param {updateRequest} updateRequest action
+ */
+function updateCustomRequestFields(evt, request, updateRequest) {
+ const val = evt.target.value;
+ let data;
+ switch (evt.target.id) {
+ case "custom-headers-value":
+ let customHeadersValue = val || "";
+ // Parse text representation of multiple HTTP headers
+ let headersArray = parseRequestText(customHeadersValue, "\\S+?", ":");
+ // Remove temp customHeadersValue while query string is parsable
+ if (customHeadersValue === "" ||
+ headersArray.length === customHeadersValue.split("\n").length) {
+ customHeadersValue = null;
+ }
+ data = {
+ requestHeaders: {
+ customHeadersValue,
+ headers: headersArray,
+ },
+ };
+ break;
+ case "custom-method-value":
+ data = { method: val.trim() };
+ break;
+ case "custom-postdata-value":
+ data = {
+ requestPostData: {
+ postData: { text: val },
+ }
+ };
+ break;
+ case "custom-query-value":
+ let customQueryValue = val || "";
+ // Parse readable text list of a query string
+ let queryArray = customQueryValue ?
+ parseRequestText(customQueryValue, ".+?", "=") : [];
+ // Write out a list of query params into a query string
+ let queryString = queryArray.map(
+ ({ name, value }) => name + "=" + value).join("&");
+ let 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);
+ }
+}
+
+module.exports = connect(
+ (state) => ({ request: getSelectedRequest(state) }),
+ (dispatch) => ({
+ removeSelectedCustomRequest: () => dispatch(Actions.removeSelectedCustomRequest()),
+ sendCustomRequest: () => dispatch(Actions.sendCustomRequest()),
+ updateRequest: (id, data, batch) => dispatch(Actions.updateRequest(id, data, batch)),
+ })
+)(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-mdn.js',
'headers-panel.js',
'params-panel.js',
'preview-panel.js',
'properties-view.js',
'response-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
@@ -129,16 +129,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_raw_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_raw_headers.js
@@ -25,23 +25,23 @@ add_task(function* () {
let origItem = RequestsMenu.getItemAtIndex(0);
wait = waitForDOM(document, ".headers-overview");
RequestsMenu.selectedItem = origItem;
yield wait;
wait = waitForDOM(document, ".raw-headers-container textarea", 2);
EventUtils.sendMouseEvent({ type: "click" },
- document.querySelectorAll(".tool-button")[1]);
+ document.querySelectorAll(".headers-summary .tool-button")[1]);
yield wait;
testShowRawHeaders(origItem);
EventUtils.sendMouseEvent({ type: "click" },
- document.querySelectorAll(".tool-button")[1]);
+ document.querySelectorAll(".headers-summary .tool-button")[1]);
testHideRawHeaders(document);
return teardown(monitor);
/*
* Tests that raw headers were displayed correctly
*/
--- 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, 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,72 +7,67 @@
* 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 { 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);
yield ContentTask.spawn(tab.linkedBrowser, requestUrl, function* (url) {
content.wrappedJSObject.performRequests(url, "triggering/preflight", "post-data");
});
yield wait;
const METHODS = ["OPTIONS", "POST"];
+ const ITEMS = METHODS.map((val, i) => RequestsMenu.getItemAtIndex(i));
// Check the requests that were sent
- for (let [i, method] of METHODS.entries()) {
- let item = RequestsMenu.getItemAtIndex(i);
- is(item.method, method, `The ${method} request has the right method`);
- is(item.url, requestUrl, `The ${method} request has the right URL`);
- }
+ ITEMS.forEach((item, i) => {
+ is(item.method, METHODS[i], `The ${item.method} request has the right method`);
+ is(item.url, requestUrl, `The ${item.method} request has the right URL`);
+ });
// Resend both requests without modification. Wait for resent OPTIONS, then POST.
// POST is supposed to have no preflight OPTIONS request this time (CORS is disabled)
- 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})`);
+ let onRequests = waitForNetworkEvents(monitor, 1, 0);
+ ITEMS.forEach((item) => {
+ info(`Selecting the ${item.method} request`);
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;
- let item = RequestsMenu.getItemAtIndex(index);
- is(item.method, method, `The ${method} request has the right method`);
- is(item.url, requestUrl, `The ${method} request has the right URL`);
- is(item.status, 200, `The ${method} response has the right status`);
+ ITEMS.forEach((item, i) => {
+ is(item.method, METHODS[i], `The ${item.method} request has the right method`);
+ is(item.url, requestUrl, `The ${item.method} request has the right URL`);
+ is(item.status, 200, `The ${item.method} response has the right status`);
- if (method === "POST") {
+ if (item.method === "POST") {
is(item.requestPostData.postData.text, "post-data",
"The POST request has the right POST data");
// eslint-disable-next-line mozilla/no-cpows-in-tests
is(item.responseContent.content.text, "Access-Control-Allow-Origin: *",
"The POST response has the right content");
}
- }
+ });
info("Finishing the test");
return teardown(monitor);
});
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -23,16 +23,17 @@
.devtools-toolbar-group {
display: flex;
flex: 0 0 auto;
flex-wrap: nowrap;
align-items: center;
}
+.custom-request-panel,
#details-pane {
/* Make details-pane's width adjustable by splitter */
min-width: 50px;
}
/**
* Collapsed details pane needs to be truly hidden to prevent both accessibility
* tools and keyboard from accessing its contents.
@@ -40,28 +41,20 @@
#details-pane.pane-collapsed {
visibility: hidden;
}
#details-pane-toggle[disabled] {
display: none;
}
-#custom-pane {
- 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);
@@ -91,17 +84,17 @@
--sort-ascending-image: url(chrome://devtools/skin/images/sort-arrows.svg#ascending);
--sort-descending-image: url(chrome://devtools/skin/images/sort-arrows.svg#descending);
}
:root.theme-firebug {
--table-splitter-color: rgba(0,0,0,0.15);
--table-zebra-background: rgba(0,0,0,0.05);
- --timing-blocked-color: rgba(235, 83, 104, 0.8); /* red */
+ --timing-blocked-color: rgba(235, 83, 104, 0.8); /* red */
--timing-dns-color: rgba(223, 128, 255, 0.8); /* pink */
--timing-connect-color: rgba(217, 102, 41, 0.8); /* orange */
--timing-send-color: rgba(70, 175, 227, 0.8); /* light blue */
--timing-wait-color: rgba(94, 136, 176, 0.8); /* blue grey */
--timing-receive-color: rgba(112, 191, 83, 0.8); /* green */
--sort-ascending-image: url(chrome://devtools/skin/images/firebug/arrow-up.svg);
--sort-descending-image: url(chrome://devtools/skin/images/firebug/arrow-down.svg);
@@ -656,24 +649,16 @@
#details-pane-toggle.pane-collapsed:-moz-locale-dir(ltr)::before,
#details-pane-toggle:-moz-locale-dir(rtl)::before {
background-image: var(--theme-pane-expand-image);
}
/* Network request details tabpanels */
-.tabpanel-content {
- background-color: var(--theme-sidebar-background);
-}
-
-.theme-dark .tabpanel-content {
- color: var(--theme-selection-color);
-}
-
.theme-firebug .variables-view-scope:focus > .title {
color: var(--theme-body-color);
}
/* Summary tabpanel */
.tabpanel-summary-container {
padding: 1px;
@@ -780,34 +765,71 @@
}
@media (min-resolution: 1.1dppx) {
.security-warning-icon {
background-image: url(images/alerticon-warning@2x.png);
}
}
-/* Custom request form */
+/* Custom request view */
+
+.custom-request-panel {
+ overflow: auto;
+ /* Full view hight - toolbar height */
+ height: calc(100vh - 24px);
+ padding: 6px 4px;
+ background-color: var(--theme-sidebar-background);
+}
+
+.theme-dark .custom-request-panel {
+ color: var(--theme-selection-color);
+}
-#custom-pane {
- padding: 0.6em 0.5em;
+.custom-request-label {
+ font-weight: 600;
+}
+
+.custom-request-panel textarea {
+ resize: none;
+ font: message-box;
+}
+
+.custom-request-panel .devtools-button {
+ margin: 3px 1px;
+ min-width: 78px;
+}
+
+.custom-header,
+.custom-method-and-url,
+.custom-request,
+.custom-section {
+ display: flex;
}
.custom-header {
+ flex-grow: 1;
font-size: 1.1em;
+ padding-top: 4px;
}
.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;
@@ -997,16 +1019,21 @@
height: 22px;
}
.requests-menu-header-button {
min-height: 22px;
padding-left: 8px;
}
+ .custom-request-panel {
+ height: 100%;
+ }
+
+ .custom-request-panel,
#details-pane {
margin: 0 !important;
/* To prevent all the margin hacks to hide the sidebar. */
}
.requests-menu-status {
max-width: none;
width: 10vw;
@@ -1207,59 +1234,64 @@
.headers-summary,
.response-summary {
display: flex;
align-items: center;
}
.headers-summary .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 {
+.tool-button {
+ background: transparent;
+ border: none;
+ border-color: var(--toolbar-button-border-color);
+ color: var(--theme-body-color);
+ min-height: 18px;
+ transition: background 0.05s ease-in-out;
+}
+
+.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 {
display: flex;
width: 100%;
}
.headers-summary .raw-headers {
width: 50%;
- padding: 0px 4px;
+ padding: 0 4px;
}
.headers-summary .raw-headers textarea {
width: 100%;
height: 50vh;
font: message-box;
resize: none;
box-sizing: border-box;
@@ -1292,16 +1324,17 @@
*/
#network-table {
display: -moz-box;
-moz-box-orient: vertical;
-moz-box-flex: 1;
overflow: hidden;
}
+#react-custom-request-panel-hook,
#statistics-panel,
#react-details-panel-hook {
display: flex;
flex-direction: column;
}
#primed-cache-chart,
#empty-cache-chart {