summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/src/reducers/requests.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/netmonitor/src/reducers/requests.js313
1 files changed, 313 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/reducers/requests.js b/devtools/client/netmonitor/src/reducers/requests.js
new file mode 100644
index 0000000000..0bc503727e
--- /dev/null
+++ b/devtools/client/netmonitor/src/reducers/requests.js
@@ -0,0 +1,313 @@
+/* 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 {
+ processNetworkUpdates,
+} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
+const {
+ ADD_REQUEST,
+ SET_EVENT_STREAM_FLAG,
+ CLEAR_REQUESTS,
+ CLONE_REQUEST,
+ CLONE_SELECTED_REQUEST,
+ OPEN_NETWORK_DETAILS,
+ REMOVE_SELECTED_CUSTOM_REQUEST,
+ RIGHT_CLICK_REQUEST,
+ SELECT_REQUEST,
+ PRESELECT_REQUEST,
+ SEND_CUSTOM_REQUEST,
+ SET_RECORDING_STATE,
+ UPDATE_REQUEST,
+} = require("resource://devtools/client/netmonitor/src/constants.js");
+
+/**
+ * This structure stores list of all HTTP requests received
+ * from the backend. It's using plain JS structures to store
+ * data instead of ImmutableJS, which is performance expensive.
+ */
+function Requests() {
+ return {
+ // Map with all requests (key = actor ID, value = request object)
+ requests: [],
+ // Selected request ID
+ selectedId: null,
+ // Right click request represents the last request that was clicked
+ clickedRequestId: null,
+ // @backward-compact { version 85 } The preselectedId can either be
+ // the actor id on old servers, or the resourceId on new ones.
+ preselectedId: null,
+ // True if the monitor is recording HTTP traffic
+ recording: true,
+ // Auxiliary fields to hold requests stats
+ firstStartedMs: +Infinity,
+ lastEndedMs: -Infinity,
+ };
+}
+
+/**
+ * This reducer is responsible for maintaining list of request
+ * within the Network panel.
+ */
+function requestsReducer(state = Requests(), action) {
+ switch (action.type) {
+ // Appending new request into the list/map.
+ case ADD_REQUEST: {
+ return addRequest(state, action);
+ }
+
+ // Update an existing request (with received data).
+ case UPDATE_REQUEST: {
+ return updateRequest(state, action);
+ }
+
+ // Add isEventStream flag to a request.
+ case SET_EVENT_STREAM_FLAG: {
+ return setEventStreamFlag(state, action);
+ }
+
+ // Remove all requests in the list. Create fresh new state
+ // object, but keep value of the `recording` field.
+ case CLEAR_REQUESTS: {
+ return {
+ ...Requests(),
+ recording: state.recording,
+ };
+ }
+
+ // Select specific request.
+ case SELECT_REQUEST: {
+ return {
+ ...state,
+ clickedRequestId: action.id,
+ selectedId: action.id,
+ };
+ }
+
+ // Clone selected request for re-send.
+ case CLONE_REQUEST: {
+ return cloneRequest(state, action.id);
+ }
+
+ case CLONE_SELECTED_REQUEST: {
+ return cloneRequest(state, state.selectedId);
+ }
+
+ case RIGHT_CLICK_REQUEST: {
+ return {
+ ...state,
+ clickedRequestId: action.id,
+ };
+ }
+
+ case PRESELECT_REQUEST: {
+ return {
+ ...state,
+ preselectedId: action.id,
+ };
+ }
+
+ // Removing temporary cloned request (created for re-send, but canceled).
+ case REMOVE_SELECTED_CUSTOM_REQUEST: {
+ return closeCustomRequest(state);
+ }
+
+ // Re-sending an existing request.
+ 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, preselectedId: action.id });
+ }
+
+ // Pause/resume button clicked.
+ case SET_RECORDING_STATE: {
+ return {
+ ...state,
+ recording: action.recording,
+ };
+ }
+
+ // Side bar with request details opened.
+ case OPEN_NETWORK_DETAILS: {
+ const nextState = { ...state };
+ if (!action.open) {
+ nextState.selectedId = null;
+ return nextState;
+ }
+
+ if (!state.selectedId && action.defaultSelectedId) {
+ nextState.selectedId = action.defaultSelectedId;
+ return nextState;
+ }
+
+ return state;
+ }
+
+ default:
+ return state;
+ }
+}
+
+// Helpers
+
+function addRequest(state, action) {
+ const nextState = { ...state };
+ // The target front is not used and cannot be serialized by redux
+ // eslint-disable-next-line no-unused-vars
+ const { targetFront, ...requestData } = action.data;
+ const newRequest = {
+ id: action.id,
+ ...requestData,
+ };
+
+ nextState.requests = [...state.requests, newRequest];
+
+ // Update the started/ended timestamps.
+ const { startedMs } = action.data;
+ if (startedMs < state.firstStartedMs) {
+ nextState.firstStartedMs = startedMs;
+ }
+ if (startedMs > state.lastEndedMs) {
+ nextState.lastEndedMs = startedMs;
+ }
+
+ // Select the request if it was preselected and there is no other selection.
+ if (state.preselectedId) {
+ if (state.preselectedId === action.id) {
+ nextState.selectedId = state.selectedId || state.preselectedId;
+ }
+ // @backward-compact { version 85 } The preselectedId can be resourceId
+ // instead of actor id when a custom request is created, and could not be
+ // selected immediately because it was not yet in the request map.
+ else if (state.preselectedId === newRequest.resourceId) {
+ nextState.selectedId = action.id;
+ }
+ nextState.preselectedId = null;
+ }
+
+ return nextState;
+}
+
+function updateRequest(state, action) {
+ const { requests, lastEndedMs } = state;
+
+ const { id } = action;
+ const index = requests.findIndex(needle => needle.id === id);
+ if (index === -1) {
+ return state;
+ }
+ const request = requests[index];
+
+ const nextRequest = {
+ ...request,
+ ...processNetworkUpdates(action.data),
+ };
+ const requestEndTime =
+ nextRequest.startedMs +
+ (nextRequest.eventTimings ? nextRequest.eventTimings.totalTime : 0);
+
+ const nextRequests = [...requests];
+ nextRequests[index] = nextRequest;
+ return {
+ ...state,
+ requests: nextRequests,
+ lastEndedMs: requestEndTime > lastEndedMs ? requestEndTime : lastEndedMs,
+ };
+}
+
+function setEventStreamFlag(state, action) {
+ const { requests } = state;
+ const { id } = action;
+ const index = requests.findIndex(needle => needle.id === id);
+ if (index === -1) {
+ return state;
+ }
+
+ const request = requests[index];
+
+ const nextRequest = {
+ ...request,
+ isEventStream: true,
+ };
+
+ const nextRequests = [...requests];
+ nextRequests[index] = nextRequest;
+ return {
+ ...state,
+ requests: nextRequests,
+ };
+}
+
+function cloneRequest(state, id) {
+ const { requests } = state;
+
+ if (!id) {
+ return state;
+ }
+
+ const clonedRequest = requests.find(needle => needle.id === id);
+ if (!clonedRequest) {
+ return state;
+ }
+
+ const newRequest = {
+ id: clonedRequest.id + "-clone",
+ method: clonedRequest.method,
+ cause: clonedRequest.cause,
+ url: clonedRequest.url,
+ urlDetails: clonedRequest.urlDetails,
+ requestHeaders: clonedRequest.requestHeaders,
+ requestPostData: clonedRequest.requestPostData,
+ requestPostDataAvailable: clonedRequest.requestPostDataAvailable,
+ requestHeadersAvailable: clonedRequest.requestHeadersAvailable,
+ isCustom: true,
+ };
+
+ return {
+ ...state,
+ requests: [...requests, newRequest],
+ selectedId: newRequest.id,
+ preselectedId: id,
+ };
+}
+
+/**
+ * Remove the currently selected custom request.
+ */
+function closeCustomRequest(state) {
+ const { requests, selectedId, preselectedId } = state;
+
+ if (!selectedId) {
+ return state;
+ }
+
+ // Find the cloned requests to be removed
+ const removedRequest = requests.find(needle => needle.id === selectedId);
+
+ // If the custom request is already in the Map, select it immediately,
+ // and reset `preselectedId` attribute.
+ // @backward-compact { version 85 } The preselectId can also be a resourceId
+ // or an actor id.
+ const customRequest = requests.find(
+ needle => needle.id === preselectedId || needle.resourceId === preselectedId
+ );
+ const hasPreselectedId = preselectedId && customRequest;
+
+ return {
+ ...state,
+ // Only custom requests can be removed
+ [removedRequest?.isCustom && "requests"]: requests.filter(
+ item => item.id !== selectedId
+ ),
+ preselectedId: hasPreselectedId ? null : preselectedId,
+ selectedId: hasPreselectedId ? customRequest.id : null,
+ };
+}
+
+module.exports = {
+ Requests,
+ requestsReducer,
+};