diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/netmonitor/src/reducers/requests.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/netmonitor/src/reducers/requests.js')
-rw-r--r-- | devtools/client/netmonitor/src/reducers/requests.js | 313 |
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, +}; |