Bug 1408182 - Replace ImmutableJS by plain JS code; r=rickychien

MozReview-Commit-ID: 4f9Bv3XDuoc

--HG--
extra : rebase_source : 834d1cfae3e32054fcaad6faa3569c4c3a885287
This commit is contained in:
Alexandre Poirot 2017-10-11 17:35:09 +02:00
parent 7afd6261d5
commit 38ebed24c2
8 changed files with 228 additions and 176 deletions

View File

@ -20,4 +20,10 @@ module.exports = {
/* eslint-disable max-len */
"mozilla/reject-some-requires": ["error", "^(chrome|chrome:.*|resource:.*|devtools/server/.*|.*\\.jsm|devtools/shared/platform/(chome|content)/.*)$"],
},
"parserOptions": {
"ecmaFeatures": {
experimentalObjectRestSpread: true,
},
},
};

View File

@ -37,7 +37,7 @@ class RequestListContent extends Component {
connector: PropTypes.object.isRequired,
columns: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
displayedRequests: PropTypes.object.isRequired,
displayedRequests: PropTypes.array.isRequired,
firstRequestStartedMillis: PropTypes.number.isRequired,
fromCache: PropTypes.bool,
onCauseBadgeMouseDown: PropTypes.func.isRequired,

View File

@ -40,7 +40,7 @@ class StatisticsPanel extends Component {
connector: PropTypes.object.isRequired,
closeStatistics: PropTypes.func.isRequired,
enableRequestFilterTypeOnly: PropTypes.func.isRequired,
requests: PropTypes.object,
requests: PropTypes.array,
};
}
@ -67,7 +67,7 @@ class StatisticsPanel extends Component {
MediaQueryList.addListener(this.onLayoutChange);
const { requests } = this.props;
let ready = requests && !requests.isEmpty() && requests.every((req) =>
let ready = requests && requests.length && requests.every((req) =>
req.contentSize !== undefined && req.mimeType && req.responseHeaders &&
req.status !== undefined && req.totalTime !== undefined
);

View File

@ -71,7 +71,7 @@ class Toolbar extends Component {
toggleBrowserCache: PropTypes.func.isRequired,
browserCacheDisabled: PropTypes.bool.isRequired,
toggleRequestFilterType: PropTypes.func.isRequired,
filteredRequests: PropTypes.object.isRequired,
filteredRequests: PropTypes.array.isRequired,
};
}

View File

@ -4,7 +4,6 @@
"use strict";
const I = require("devtools/client/shared/vendor/immutable");
const {
getUrlDetails,
processNetworkUpdates,
@ -21,59 +20,173 @@ const {
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,
endedMillis: undefined,
method: undefined,
url: undefined,
urlDetails: undefined,
remotePort: undefined,
remoteAddress: undefined,
isXHR: undefined,
cause: undefined,
fromCache: undefined,
fromServiceWorker: undefined,
status: undefined,
statusText: undefined,
httpVersion: undefined,
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,
responseContentAvailable: false,
formDataSections: undefined,
});
/**
* 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: mapNew(),
// Selected request ID
selectedId: null,
preselectedId: null,
// True if the monitor is recording HTTP traffic
recording: true,
// Auxiliary fields to hold requests stats
firstStartedMillis: +Infinity,
lastEndedMillis: -Infinity,
};
}
const Requests = I.Record({
// The collection of requests (keyed by id)
requests: I.Map(),
// Selection state
selectedId: null,
preselectedId: null,
// Auxiliary fields to hold requests stats
firstStartedMillis: +Infinity,
lastEndedMillis: -Infinity,
// Recording state
recording: true,
});
/**
* 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: {
let nextState = { ...state };
let newRequest = {
id: action.id,
...action.data,
urlDetails: getUrlDetails(action.data.url),
};
nextState.requests = mapSet(state.requests, newRequest.id, newRequest);
// Update the started/ended timestamps.
let { startedMillis } = action.data;
if (startedMillis < state.firstStartedMillis) {
nextState.firstStartedMillis = startedMillis;
}
if (startedMillis > state.lastEndedMillis) {
nextState.lastEndedMillis = startedMillis;
}
// Select the request if it was preselected and there is no other selection.
if (state.preselectedId && state.preselectedId === action.id) {
nextState.selectedId = state.selectedId || state.preselectedId;
nextState.preselectedId = null;
}
return nextState;
}
// Update an existing request (with received data).
case UPDATE_REQUEST: {
let { requests, lastEndedMillis } = state;
let request = requests.get(action.id);
if (!request) {
return state;
}
request = {
...request,
...processNetworkUpdates(action.data),
};
return {
...state,
requests: mapSet(state.requests, action.id, request),
lastEndedMillis: lastEndedMillis,
};
}
// 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,
selectedId: action.id,
};
}
// Clone selected request for re-send.
case CLONE_SELECTED_REQUEST: {
let { requests, selectedId } = state;
if (!selectedId) {
return state;
}
let clonedRequest = requests.get(selectedId);
if (!clonedRequest) {
return state;
}
let newRequest = {
id: clonedRequest.id + "-clone",
method: clonedRequest.method,
url: clonedRequest.url,
urlDetails: clonedRequest.urlDetails,
requestHeaders: clonedRequest.requestHeaders,
requestPostData: clonedRequest.requestPostData,
isCustom: true
};
return {
...state,
requests: mapSet(requests, newRequest.id, newRequest),
selectedId: newRequest.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.set("preselectedId", action.id));
}
// Pause/resume button clicked.
case TOGGLE_RECORDING: {
return {
...state,
recording: !state.recording,
};
}
// Side bar with request details opened.
case OPEN_NETWORK_DETAILS: {
let nextState = { ...state };
if (!action.open) {
nextState.selectedId = null;
return nextState;
}
if (!state.selectedId && !state.requests.isEmpty()) {
nextState.selectedId = [...state.requests.values()][0].id;
return nextState;
}
return state;
}
default:
return state;
}
}
// Helpers
/**
* Remove the currently selected custom request.
@ -92,119 +205,41 @@ function closeCustomRequest(state) {
return state;
}
return state.withMutations(st => {
st.requests = st.requests.delete(selectedId);
st.selectedId = null;
});
return {
...state,
requests: mapDelete(state.requests, selectedId),
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) }
));
st.requests = st.requests.set(newRequest.id, newRequest);
// Immutability helpers
// FIXME The following helper API need refactoring, see bug 1418969.
// Update the started/ended timestamps
let { startedMillis } = action.data;
if (startedMillis < st.firstStartedMillis) {
st.firstStartedMillis = startedMillis;
}
if (startedMillis > st.lastEndedMillis) {
st.lastEndedMillis = startedMillis;
}
/**
* Clone an existing map.
*/
function mapNew(map) {
let newMap = new Map(map);
newMap.isEmpty = () => newMap.size == 0;
newMap.valueSeq = () => [...newMap.values()];
return newMap;
}
// 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 CLEAR_REQUESTS: {
return new Requests({
recording: state.recording
});
}
case CLONE_SELECTED_REQUEST: {
let { requests, selectedId } = state;
/**
* Append new item into existing map and return new map.
*/
function mapSet(map, key, value) {
let newMap = mapNew(map);
return newMap.set(key, value);
}
if (!selectedId) {
return state;
}
let clonedRequest = requests.get(selectedId);
if (!clonedRequest) {
return state;
}
let newRequest = new Request({
id: clonedRequest.id + "-clone",
method: clonedRequest.method,
url: clonedRequest.url,
urlDetails: clonedRequest.urlDetails,
requestHeaders: clonedRequest.requestHeaders,
requestPostData: clonedRequest.requestPostData,
isCustom: true
});
return state.withMutations(st => {
st.requests = requests.set(newRequest.id, newRequest);
st.selectedId = newRequest.id;
});
}
case OPEN_NETWORK_DETAILS: {
if (!action.open) {
return state.set("selectedId", null);
}
if (!state.selectedId && !state.requests.isEmpty()) {
return state.set("selectedId", state.requests.first().id);
}
return state;
}
case REMOVE_SELECTED_CUSTOM_REQUEST: {
return closeCustomRequest(state);
}
case SELECT_REQUEST: {
return state.set("selectedId", action.id);
}
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 TOGGLE_RECORDING: {
return state.set("recording", !state.recording);
}
case UPDATE_REQUEST: {
let { requests, lastEndedMillis } = state;
let updatedRequest = requests.get(action.id);
if (!updatedRequest) {
return state;
}
updatedRequest = updatedRequest.withMutations(request => {
let values = processNetworkUpdates(action.data);
request = Object.assign(request, values);
});
return state.withMutations(st => {
st.requests = requests.set(updatedRequest.id, updatedRequest);
st.lastEndedMillis = lastEndedMillis;
});
}
default:
return state;
}
/**
* Remove an item from existing map and return new map.
*/
function mapDelete(map, key) {
let newMap = mapNew(map);
newMap.requests.delete(key);
return newMap;
}
module.exports = {

View File

@ -56,9 +56,9 @@ const getTypeFilterFn = createSelector(
);
const getSortFn = createSelector(
state => state.requests.requests,
state => state.requests,
state => state.sort,
(requests, sort) => {
({ requests }, sort) => {
const sorter = Sorters[sort.type || "waterfall"];
const ascending = sort.ascending ? +1 : -1;
return (a, b) => ascending * sortWithClones(requests, sorter, a, b);
@ -66,23 +66,34 @@ const getSortFn = createSelector(
);
const getSortedRequests = createSelector(
state => state.requests.requests,
state => state.requests,
getSortFn,
(requests, sortFn) => requests.valueSeq().sort(sortFn).toList()
({ requests }, sortFn) => {
let arr = requests.valueSeq().sort(sortFn);
arr.get = index => arr[index];
arr.isEmpty = () => this.length == 0;
arr.size = arr.length;
return arr;
}
);
const getDisplayedRequests = createSelector(
state => state.requests.requests,
state => state.requests,
getFilterFn,
getSortFn,
(requests, filterFn, sortFn) => requests.valueSeq()
.filter(filterFn).sort(sortFn).toList()
({ requests }, filterFn, sortFn) => {
let arr = requests.valueSeq().filter(filterFn).sort(sortFn);
arr.get = index => arr[index];
arr.isEmpty = () => this.length == 0;
arr.size = arr.length;
return arr;
}
);
const getTypeFilteredRequests = createSelector(
state => state.requests.requests,
state => state.requests,
getTypeFilterFn,
(requests, filterFn) => requests.valueSeq().filter(filterFn).toList()
({ requests }, filterFn) => requests.valueSeq().filter(filterFn)
);
const getDisplayedRequestsSummary = createSelector(

View File

@ -5,7 +5,7 @@
"use strict";
function getDisplayedTimingMarker(state, marker) {
return state.timingMarkers.get(marker) - state.requests.get("firstStartedMillis");
return state.timingMarkers.get(marker) - state.requests.firstStartedMillis;
}
module.exports = {

View File

@ -8,7 +8,7 @@ const { REQUESTS_WATERFALL } = require("../constants");
const { getDisplayedRequests } = require("./requests");
function isNetworkDetailsToggleButtonDisabled(state) {
return getDisplayedRequests(state).isEmpty();
return getDisplayedRequests(state).length == 0;
}
const EPSILON = 0.001;