tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

requests.js (8787B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const {
      8  processNetworkUpdates,
      9 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
     10 const {
     11  ADD_REQUEST,
     12  SET_EVENT_STREAM_FLAG,
     13  CLEAR_REQUESTS,
     14  CLONE_REQUEST,
     15  CLONE_SELECTED_REQUEST,
     16  OPEN_NETWORK_DETAILS,
     17  REMOVE_SELECTED_CUSTOM_REQUEST,
     18  RIGHT_CLICK_REQUEST,
     19  SELECT_REQUEST,
     20  PRESELECT_REQUEST,
     21  SEND_CUSTOM_REQUEST,
     22  SET_RECORDING_STATE,
     23  UPDATE_REQUEST,
     24 } = require("resource://devtools/client/netmonitor/src/constants.js");
     25 
     26 /**
     27 * This structure stores list of all HTTP requests received
     28 * from the backend. It's using plain JS structures to store
     29 * data instead of ImmutableJS, which is performance expensive.
     30 */
     31 function Requests() {
     32  return {
     33    // Map with all requests (key = actor ID, value = request object)
     34    requests: [],
     35    // Selected request ID
     36    selectedId: null,
     37    // Right click request represents the last request that was clicked
     38    clickedRequestId: null,
     39    // @backward-compact { version 85 } The preselectedId can either be
     40    // the actor id on old servers, or the resourceId on new ones.
     41    preselectedId: null,
     42    // True if the monitor is recording HTTP traffic
     43    recording: true,
     44    // Auxiliary fields to hold requests stats
     45    firstStartedMs: +Infinity,
     46    lastEndedMs: -Infinity,
     47  };
     48 }
     49 
     50 /**
     51 * This reducer is responsible for maintaining list of request
     52 * within the Network panel.
     53 */
     54 // eslint-disable-next-line complexity
     55 function requestsReducer(state = Requests(), action) {
     56  switch (action.type) {
     57    // Appending new request into the list/map.
     58    case ADD_REQUEST: {
     59      return addRequest(state, action);
     60    }
     61 
     62    // Update an existing request (with received data).
     63    case UPDATE_REQUEST: {
     64      return updateRequest(state, action);
     65    }
     66 
     67    // Add isEventStream flag to a request.
     68    case SET_EVENT_STREAM_FLAG: {
     69      return setEventStreamFlag(state, action);
     70    }
     71 
     72    // Remove all requests in the list. Create fresh new state
     73    // object, but keep value of the `recording` field.
     74    case CLEAR_REQUESTS: {
     75      return {
     76        ...Requests(),
     77        recording: state.recording,
     78      };
     79    }
     80 
     81    // Select specific request.
     82    case SELECT_REQUEST: {
     83      if (
     84        state.clickedRequestId == action.id &&
     85        state.selectedId == action.id
     86      ) {
     87        return state;
     88      }
     89      return {
     90        ...state,
     91        clickedRequestId: action.id,
     92        selectedId: action.id,
     93      };
     94    }
     95 
     96    // Clone selected request for re-send.
     97    case CLONE_REQUEST: {
     98      return cloneRequest(state, action.id);
     99    }
    100 
    101    case CLONE_SELECTED_REQUEST: {
    102      return cloneRequest(state, state.selectedId);
    103    }
    104 
    105    case RIGHT_CLICK_REQUEST: {
    106      if (state.clickedRequestId == action.id) {
    107        return state;
    108      }
    109      return {
    110        ...state,
    111        clickedRequestId: action.id,
    112      };
    113    }
    114 
    115    case PRESELECT_REQUEST: {
    116      if (state.preselectedId == action.id) {
    117        return state;
    118      }
    119      return {
    120        ...state,
    121        preselectedId: action.id,
    122      };
    123    }
    124 
    125    // Removing temporary cloned request (created for re-send, but canceled).
    126    case REMOVE_SELECTED_CUSTOM_REQUEST: {
    127      return closeCustomRequest(state);
    128    }
    129 
    130    // Re-sending an existing request.
    131    case SEND_CUSTOM_REQUEST: {
    132      // When a new request with a given id is added in future, select it immediately.
    133      // where we know in advance the ID of the request, at a time when it
    134      // wasn't sent yet.
    135      return closeCustomRequest({ ...state, preselectedId: action.id });
    136    }
    137 
    138    // Pause/resume button clicked.
    139    case SET_RECORDING_STATE: {
    140      if (state.recording == action.recording) {
    141        return state;
    142      }
    143      return {
    144        ...state,
    145        recording: action.recording,
    146      };
    147    }
    148 
    149    // Side bar with request details opened.
    150    case OPEN_NETWORK_DETAILS: {
    151      if (!action.open) {
    152        if (state.selectedId == null) {
    153          return state;
    154        }
    155        return {
    156          ...state,
    157          selectedId: null,
    158        };
    159      }
    160 
    161      if (!state.selectedId && action.defaultSelectedId != state.selectedId) {
    162        return {
    163          ...state,
    164          selectedId: action.defaultSelectedId,
    165        };
    166      }
    167 
    168      return state;
    169    }
    170 
    171    default:
    172      return state;
    173  }
    174 }
    175 
    176 // Helpers
    177 
    178 function addRequest(state, action) {
    179  const nextState = { ...state };
    180  // The target front is not used and cannot be serialized by redux
    181  // eslint-disable-next-line no-unused-vars
    182  const { targetFront, ...requestData } = action.data;
    183  const newRequest = {
    184    id: action.id,
    185    ...requestData,
    186  };
    187 
    188  nextState.requests = [...state.requests, newRequest];
    189 
    190  // Update the started/ended timestamps.
    191  const { startedMs } = action.data;
    192  if (startedMs < state.firstStartedMs) {
    193    nextState.firstStartedMs = startedMs;
    194  }
    195  if (startedMs > state.lastEndedMs) {
    196    nextState.lastEndedMs = startedMs;
    197  }
    198 
    199  // Select the request if it was preselected and there is no other selection.
    200  if (state.preselectedId) {
    201    if (state.preselectedId === action.id) {
    202      nextState.selectedId = state.selectedId || state.preselectedId;
    203    }
    204    // @backward-compact { version 85 } The preselectedId can be resourceId
    205    // instead of actor id when a custom request is created, and could not be
    206    // selected immediately because it was not yet in the request map.
    207    else if (state.preselectedId === newRequest.resourceId) {
    208      nextState.selectedId = action.id;
    209    }
    210    nextState.preselectedId = null;
    211  }
    212 
    213  return nextState;
    214 }
    215 
    216 function updateRequest(state, action) {
    217  const { requests, lastEndedMs } = state;
    218 
    219  const { id } = action;
    220  const index = requests.findIndex(needle => needle.id === id);
    221  if (index === -1) {
    222    return state;
    223  }
    224  const request = requests[index];
    225 
    226  const nextRequest = {
    227    ...request,
    228    ...processNetworkUpdates(action.data),
    229  };
    230  const requestEndTime =
    231    nextRequest.startedMs +
    232    (nextRequest.eventTimings ? nextRequest.eventTimings.totalTime : 0);
    233 
    234  const nextRequests = [...requests];
    235  nextRequests[index] = nextRequest;
    236  return {
    237    ...state,
    238    requests: nextRequests,
    239    lastEndedMs: requestEndTime > lastEndedMs ? requestEndTime : lastEndedMs,
    240  };
    241 }
    242 
    243 function setEventStreamFlag(state, action) {
    244  const { requests } = state;
    245  const { id } = action;
    246  const index = requests.findIndex(needle => needle.id === id);
    247  if (index === -1) {
    248    return state;
    249  }
    250 
    251  const request = requests[index];
    252 
    253  const nextRequest = {
    254    ...request,
    255    isEventStream: true,
    256  };
    257 
    258  const nextRequests = [...requests];
    259  nextRequests[index] = nextRequest;
    260  return {
    261    ...state,
    262    requests: nextRequests,
    263  };
    264 }
    265 
    266 function cloneRequest(state, id) {
    267  const { requests } = state;
    268 
    269  if (!id) {
    270    return state;
    271  }
    272 
    273  const clonedRequest = requests.find(needle => needle.id === id);
    274  if (!clonedRequest) {
    275    return state;
    276  }
    277 
    278  const newRequest = {
    279    id: clonedRequest.id + "-clone",
    280    method: clonedRequest.method,
    281    cause: clonedRequest.cause,
    282    url: clonedRequest.url,
    283    urlDetails: clonedRequest.urlDetails,
    284    requestHeaders: clonedRequest.requestHeaders,
    285    requestPostData: clonedRequest.requestPostData,
    286    requestPostDataAvailable: clonedRequest.requestPostDataAvailable,
    287    requestHeadersAvailable: clonedRequest.requestHeadersAvailable,
    288    isCustom: true,
    289  };
    290 
    291  return {
    292    ...state,
    293    requests: [...requests, newRequest],
    294    selectedId: newRequest.id,
    295    preselectedId: id,
    296  };
    297 }
    298 
    299 /**
    300 * Remove the currently selected custom request.
    301 */
    302 function closeCustomRequest(state) {
    303  const { requests, selectedId, preselectedId } = state;
    304 
    305  if (!selectedId) {
    306    return state;
    307  }
    308 
    309  // Find the cloned requests to be removed
    310  const removedRequest = requests.find(needle => needle.id === selectedId);
    311 
    312  // If the custom request is already in the Map, select it immediately,
    313  // and reset `preselectedId` attribute.
    314  // @backward-compact { version 85 } The preselectId can also be a resourceId
    315  // or an actor id.
    316  const customRequest = requests.find(
    317    needle => needle.id === preselectedId || needle.resourceId === preselectedId
    318  );
    319  const hasPreselectedId = preselectedId && customRequest;
    320 
    321  return {
    322    ...state,
    323    // Only custom requests can be removed
    324    [removedRequest?.isCustom && "requests"]: requests.filter(
    325      item => item.id !== selectedId
    326    ),
    327    preselectedId: hasPreselectedId ? null : preselectedId,
    328    selectedId: hasPreselectedId ? customRequest.id : null,
    329  };
    330 }
    331 
    332 module.exports = {
    333  Requests,
    334  requestsReducer,
    335 };