search.js (8219B)
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 ADD_SEARCH_QUERY, 9 ADD_SEARCH_RESULT, 10 CLEAR_SEARCH_RESULTS, 11 ADD_ONGOING_SEARCH, 12 OPEN_ACTION_BAR, 13 UPDATE_SEARCH_STATUS, 14 SEARCH_STATUS, 15 SET_TARGET_SEARCH_RESULT, 16 SELECT_ACTION_BAR_TAB, 17 TOGGLE_SEARCH_CASE_SENSITIVE_SEARCH, 18 PANELS, 19 } = require("resource://devtools/client/netmonitor/src/constants.js"); 20 21 const { 22 getDisplayedRequests, 23 getOngoingSearch, 24 getSearchStatus, 25 getRequestById, 26 } = require("resource://devtools/client/netmonitor/src/selectors/index.js"); 27 28 const { 29 selectRequest, 30 } = require("resource://devtools/client/netmonitor/src/actions/selection.js"); 31 const { 32 selectDetailsPanelTab, 33 } = require("resource://devtools/client/netmonitor/src/actions/ui.js"); 34 const { 35 fetchNetworkUpdatePacket, 36 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 37 const { 38 searchInResource, 39 } = require("resource://devtools/client/netmonitor/src/workers/search/index.js"); 40 41 /** 42 * Search through all resources. This is the main action exported 43 * from this module and consumed by Network panel UI. 44 */ 45 function search(connector, query) { 46 let canceled = false; 47 48 // Instantiate an `ongoingSearch` function/object. It's responsible 49 // for triggering set of asynchronous steps like fetching 50 // data from the backend and performing search over it. 51 // This `ongoingSearch` is stored in the Search reducer, so it can 52 // be canceled if needed (e.g. when new search is executed). 53 const newOngoingSearch = async ({ dispatch, getState }) => { 54 const state = getState(); 55 56 dispatch(stopOngoingSearch()); 57 58 await dispatch(addOngoingSearch(newOngoingSearch)); 59 await dispatch(clearSearchResults()); 60 await dispatch(addSearchQuery(query)); 61 62 dispatch(updateSearchStatus(SEARCH_STATUS.FETCHING)); 63 64 // Loop over all displayed resources (in the sorted order), 65 // fetch all the details data and run search worker that 66 // search through the resource structure. 67 const requests = getDisplayedRequests(state); 68 for (const request of requests) { 69 if (canceled) { 70 return; 71 } 72 73 // Ignore any request which have a now defunct target 74 // as we wouldn't be able to fetch any lazy data for them. 75 // Inconditionally allow searching for navigation request as it is bound to the previous target front 76 if ( 77 request.innerWindowId && 78 !request.isNavigationRequest && 79 !connector.commands.watcherFront.getWindowGlobalTargetByInnerWindowId( 80 request.innerWindowId 81 ) 82 ) { 83 continue; 84 } 85 86 // Fetch all data for the resource. 87 await loadResource(connector, request); 88 if (canceled) { 89 return; 90 } 91 92 // The state changed, so make sure to get fresh new reference 93 // to the updated resource object. 94 const updatedResource = getRequestById(getState(), request.id); 95 await dispatch(searchResource(updatedResource, query)); 96 } 97 98 dispatch(updateSearchStatus(SEARCH_STATUS.DONE)); 99 }; 100 101 // Implement support for canceling (used e.g. when a new search 102 // is executed or the user stops the searching manually). 103 newOngoingSearch.cancel = () => { 104 canceled = true; 105 }; 106 107 newOngoingSearch.isCanceled = () => { 108 return canceled; 109 }; 110 111 return newOngoingSearch; 112 } 113 114 /** 115 * Fetch all data related to the specified resource from the backend. 116 */ 117 async function loadResource(connector, resource) { 118 const updateTypes = [ 119 "responseHeaders", 120 "requestHeaders", 121 "responseCookies", 122 "requestCookies", 123 "requestPostData", 124 "responseContent", 125 "responseCache", 126 "stackTrace", 127 "securityInfo", 128 ]; 129 130 return fetchNetworkUpdatePacket(connector.requestData, resource, updateTypes); 131 } 132 133 /** 134 * Search through all data within the specified resource. 135 */ 136 function searchResource(resource, query) { 137 return async ({ dispatch, getState }) => { 138 const state = getState(); 139 const ongoingSearch = getOngoingSearch(state); 140 141 const modifiers = { 142 caseSensitive: state.search.caseSensitive, 143 }; 144 145 // Run search in a worker and wait for the results. The return 146 // value is an array with search occurrences. 147 const result = await searchInResource(resource, query, modifiers); 148 149 if (!result.length || ongoingSearch.isCanceled()) { 150 return; 151 } 152 153 dispatch(addSearchResult(resource, result)); 154 }; 155 } 156 157 /** 158 * Add search query to the reducer. 159 */ 160 function addSearchResult(resource, result) { 161 return { 162 type: ADD_SEARCH_RESULT, 163 resource, 164 result, 165 }; 166 } 167 168 /** 169 * Add search query to the reducer. 170 */ 171 function addSearchQuery(query) { 172 return { 173 type: ADD_SEARCH_QUERY, 174 query, 175 }; 176 } 177 178 /** 179 * Clear all search results. 180 */ 181 function clearSearchResults() { 182 return { 183 type: CLEAR_SEARCH_RESULTS, 184 }; 185 } 186 187 /** 188 * Used to clear and cancel an ongoing search. 189 * 190 * @returns {Function} 191 */ 192 function clearSearchResultAndCancel() { 193 return ({ dispatch }) => { 194 dispatch(stopOngoingSearch()); 195 dispatch(clearSearchResults()); 196 }; 197 } 198 199 /** 200 * Update status of the current search. 201 */ 202 function updateSearchStatus(status) { 203 return { 204 type: UPDATE_SEARCH_STATUS, 205 status, 206 }; 207 } 208 209 /** 210 * Close the entire search panel. 211 */ 212 function closeSearch() { 213 return ({ dispatch }) => { 214 dispatch(stopOngoingSearch()); 215 dispatch({ type: OPEN_ACTION_BAR, open: false }); 216 }; 217 } 218 219 /** 220 * Open the entire search panel 221 * 222 * @returns {Function} 223 */ 224 function openSearch() { 225 return ({ dispatch }) => { 226 dispatch({ type: OPEN_ACTION_BAR, open: true }); 227 228 dispatch({ 229 type: SELECT_ACTION_BAR_TAB, 230 id: PANELS.SEARCH, 231 }); 232 }; 233 } 234 235 /** 236 * Toggles case sensitive search 237 * 238 * @returns {Function} 239 */ 240 function toggleCaseSensitiveSearch() { 241 return ({ dispatch }) => { 242 dispatch({ type: TOGGLE_SEARCH_CASE_SENSITIVE_SEARCH }); 243 }; 244 } 245 246 /** 247 * Toggle visibility of search panel in network panel 248 */ 249 function toggleSearchPanel() { 250 return ({ dispatch, getState }) => { 251 const state = getState(); 252 253 state.ui.networkActionOpen && 254 state.ui.selectedActionBarTabId === PANELS.SEARCH 255 ? dispatch({ type: OPEN_ACTION_BAR, open: false }) 256 : dispatch({ type: OPEN_ACTION_BAR, open: true }); 257 258 dispatch({ 259 type: SELECT_ACTION_BAR_TAB, 260 id: PANELS.SEARCH, 261 }); 262 }; 263 } 264 265 /** 266 * Append new search object into the reducer. The search object 267 * is cancellable and so, it implements `cancel` method. 268 */ 269 function addOngoingSearch(ongoingSearch) { 270 return { 271 type: ADD_ONGOING_SEARCH, 272 ongoingSearch, 273 }; 274 } 275 276 /** 277 * Cancel the current ongoing search. 278 */ 279 function stopOngoingSearch() { 280 return ({ dispatch, getState }) => { 281 const state = getState(); 282 const ongoingSearch = getOngoingSearch(state); 283 const status = getSearchStatus(state); 284 285 if (ongoingSearch && status !== SEARCH_STATUS.DONE) { 286 ongoingSearch.cancel(); 287 dispatch(updateSearchStatus(SEARCH_STATUS.CANCELED)); 288 } 289 }; 290 } 291 292 /** 293 * This action is fired when the user selects a search result 294 * within the Search panel. It opens the details side bar and 295 * selects the right side panel to show the context of the 296 * clicked search result. 297 */ 298 function navigate(searchResult) { 299 return ({ dispatch }) => { 300 // Store target search result in Search reducer. It's used 301 // for search result navigation within the side panels. 302 dispatch(setTargetSearchResult(searchResult)); 303 304 // Preselect the required side panel. 305 dispatch(selectDetailsPanelTab(searchResult.panel)); 306 307 // Select related request in the UI (it also opens the 308 // right side bar automatically). 309 dispatch(selectRequest(searchResult.parentResource.id)); 310 }; 311 } 312 313 function setTargetSearchResult(searchResult) { 314 return { 315 type: SET_TARGET_SEARCH_RESULT, 316 searchResult, 317 }; 318 } 319 320 module.exports = { 321 search, 322 closeSearch, 323 openSearch, 324 clearSearchResults, 325 addSearchQuery, 326 toggleSearchPanel, 327 navigate, 328 setTargetSearchResult, 329 toggleCaseSensitiveSearch, 330 clearSearchResultAndCancel, 331 stopOngoingSearch, 332 };