requests.js (5739B)
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 createSelector, 9 } = require("resource://devtools/client/shared/vendor/reselect.js"); 10 const { 11 Filters, 12 isFreetextMatch, 13 } = require("resource://devtools/client/netmonitor/src/utils/filter-predicates.js"); 14 const { 15 Sorters, 16 } = require("resource://devtools/client/netmonitor/src/utils/sort-predicates.js"); 17 18 /** 19 * Take clones into account when sorting. 20 * If a request is a clone, use the original request for comparison. 21 * If one of the compared request is a clone of the other, sort them next to each other. 22 */ 23 function sortWithClones(requests, sorter, a, b) { 24 const aId = a.id, 25 bId = b.id; 26 27 if (aId.endsWith("-clone")) { 28 const aOrigId = aId.replace(/-clone$/, ""); 29 if (aOrigId === bId) { 30 return +1; 31 } 32 a = requests.find(item => item.id === aOrigId); 33 } 34 35 if (bId.endsWith("-clone")) { 36 const bOrigId = bId.replace(/-clone$/, ""); 37 if (bOrigId === aId) { 38 return -1; 39 } 40 b = requests.find(item => item.id === bOrigId); 41 } 42 43 const defaultSorter = () => false; 44 return sorter ? sorter(a, b) : defaultSorter; 45 } 46 47 const getActiveFilters = createSelector( 48 state => state.filters.requestFilterTypes, 49 requestFilterTypes => 50 Object.keys(requestFilterTypes) 51 .filter(type => requestFilterTypes[type] && Filters[type]) 52 .map(type => Filters[type]) 53 ); 54 55 /** 56 * Take clones into account when filtering. If a request is 57 * a clone, it's not filtered out. 58 */ 59 const getFilterWithCloneFn = createSelector( 60 getActiveFilters, 61 state => state.filters.requestFilterText, 62 (activeFilters, requestFilterText) => { 63 return r => { 64 const isClone = r.id.endsWith("-clone"); 65 const matchesType = activeFilters.some(filter => filter(r)); 66 return (isClone || matchesType) && isFreetextMatch(r, requestFilterText); 67 }; 68 } 69 ); 70 71 const getTypeFilterFn = createSelector( 72 getActiveFilters, 73 activeFilters => r => activeFilters.some(filter => filter(r)) 74 ); 75 76 const getSortFn = createSelector( 77 state => state.requests.requests, 78 state => state.sort, 79 (requests, sort) => { 80 const sorter = Sorters[sort.type || "waterfall"]; 81 const ascending = sort.ascending ? +1 : -1; 82 return (a, b) => ascending * sortWithClones(requests, sorter, a, b); 83 } 84 ); 85 86 const getSortedRequests = createSelector( 87 state => state.requests.requests, 88 getSortFn, 89 (requests, sortFn) => [...requests].sort(sortFn) 90 ); 91 92 const getDisplayedRequests = createSelector( 93 state => state.requests.requests, 94 getFilterWithCloneFn, 95 getSortFn, 96 (requests, filterFn, sortFn) => requests.filter(filterFn).sort(sortFn) 97 ); 98 99 const getTypeFilteredRequests = createSelector( 100 state => state.requests.requests, 101 getTypeFilterFn, 102 (requests, filterFn) => requests.filter(filterFn) 103 ); 104 105 const getDisplayedRequestsSummary = createSelector( 106 getDisplayedRequests, 107 state => state.requests.lastEndedMs - state.requests.firstStartedMs, 108 (requests, totalMs) => { 109 if (requests.length === 0) { 110 return { count: 0, bytes: 0, ms: 0 }; 111 } 112 113 const totalBytes = requests.reduce( 114 (totals, item) => { 115 if (typeof item.contentSize == "number") { 116 totals.contentSize += item.contentSize; 117 } 118 119 if ( 120 typeof item.transferredSize == "number" && 121 !(item.fromCache || item.fromServiceWorker || item.status === "304") 122 ) { 123 totals.transferredSize += item.transferredSize; 124 } 125 126 return totals; 127 }, 128 { contentSize: 0, transferredSize: 0 } 129 ); 130 131 return { 132 count: requests.length, 133 contentSize: totalBytes.contentSize, 134 ms: totalMs, 135 transferredSize: totalBytes.transferredSize, 136 }; 137 } 138 ); 139 140 const getSelectedRequest = createSelector( 141 state => state.requests.requests, 142 state => state.requests.selectedId, 143 (requests, selectedId) => 144 selectedId ? requests.find(item => item.id === selectedId) : undefined 145 ); 146 147 const isSelectedRequestVisible = createSelector( 148 state => state.requests.selectedId, 149 getDisplayedRequests, 150 (selectedId, displayedRequests) => 151 displayedRequests.some(r => r.id === selectedId) 152 ); 153 154 function getRequestById(state, id) { 155 return state.requests.requests.find(item => item.id === id); 156 } 157 158 function getRequestByChannelId(state, channelId) { 159 return [...state.requests.requests.values()].find( 160 r => r.resourceId == channelId 161 ); 162 } 163 164 function getDisplayedRequestById(state, id) { 165 return getDisplayedRequests(state).find(r => r.id === id); 166 } 167 168 /** 169 * Returns the current recording boolean state (HTTP traffic is 170 * monitored or not monitored) 171 */ 172 function getRecordingState(state) { 173 return state.requests.recording; 174 } 175 176 const getClickedRequest = createSelector( 177 state => state.requests.requests, 178 state => state.requests.clickedRequestId, 179 (requests, clickedRequestId) => 180 requests.find(request => request.id == clickedRequestId) 181 ); 182 183 /** 184 * If a network override is set for the provided url, returns the override path. 185 * Otherwise returns null. 186 */ 187 function getOverriddenUrl(toolboxState, url) { 188 return toolboxState.networkOverrides.mutableOverrides[url] || null; 189 } 190 191 function hasOverride(toolboxState) { 192 return !!Object.keys(toolboxState.networkOverrides.mutableOverrides).length; 193 } 194 195 module.exports = { 196 getClickedRequest, 197 getDisplayedRequestById, 198 getDisplayedRequests, 199 getDisplayedRequestsSummary, 200 getOverriddenUrl, 201 getRecordingState, 202 getRequestById, 203 getRequestByChannelId, 204 getSelectedRequest, 205 getSortedRequests, 206 getTypeFilteredRequests, 207 hasOverride, 208 isSelectedRequestVisible, 209 };