Source code

Revision control

Copy as Markdown

Other Tools

/* 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 {
ADD_SEARCH_QUERY,
ADD_SEARCH_RESULT,
CLEAR_SEARCH_RESULTS,
ADD_ONGOING_SEARCH,
OPEN_ACTION_BAR,
UPDATE_SEARCH_STATUS,
SEARCH_STATUS,
SET_TARGET_SEARCH_RESULT,
SELECT_ACTION_BAR_TAB,
TOGGLE_SEARCH_CASE_SENSITIVE_SEARCH,
PANELS,
} = require("resource://devtools/client/netmonitor/src/constants.js");
const {
getDisplayedRequests,
getOngoingSearch,
getSearchStatus,
getRequestById,
} = require("resource://devtools/client/netmonitor/src/selectors/index.js");
const {
selectRequest,
} = require("resource://devtools/client/netmonitor/src/actions/selection.js");
const {
selectDetailsPanelTab,
} = require("resource://devtools/client/netmonitor/src/actions/ui.js");
const {
fetchNetworkUpdatePacket,
} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
const {
searchInResource,
} = require("resource://devtools/client/netmonitor/src/workers/search/index.js");
/**
* Search through all resources. This is the main action exported
* from this module and consumed by Network panel UI.
*/
function search(connector, query) {
let canceled = false;
// Instantiate an `ongoingSearch` function/object. It's responsible
// for triggering set of asynchronous steps like fetching
// data from the backend and performing search over it.
// This `ongoingSearch` is stored in the Search reducer, so it can
// be canceled if needed (e.g. when new search is executed).
const newOngoingSearch = async ({ dispatch, getState }) => {
const state = getState();
dispatch(stopOngoingSearch());
await dispatch(addOngoingSearch(newOngoingSearch));
await dispatch(clearSearchResults());
await dispatch(addSearchQuery(query));
dispatch(updateSearchStatus(SEARCH_STATUS.FETCHING));
// Loop over all displayed resources (in the sorted order),
// fetch all the details data and run search worker that
// search through the resource structure.
const requests = getDisplayedRequests(state);
for (const request of requests) {
if (canceled) {
return;
}
// Fetch all data for the resource.
await loadResource(connector, request);
if (canceled) {
return;
}
// The state changed, so make sure to get fresh new reference
// to the updated resource object.
const updatedResource = getRequestById(getState(), request.id);
await dispatch(searchResource(updatedResource, query));
}
dispatch(updateSearchStatus(SEARCH_STATUS.DONE));
};
// Implement support for canceling (used e.g. when a new search
// is executed or the user stops the searching manually).
newOngoingSearch.cancel = () => {
canceled = true;
};
newOngoingSearch.isCanceled = () => {
return canceled;
};
return newOngoingSearch;
}
/**
* Fetch all data related to the specified resource from the backend.
*/
async function loadResource(connector, resource) {
const updateTypes = [
"responseHeaders",
"requestHeaders",
"responseCookies",
"requestCookies",
"requestPostData",
"responseContent",
"responseCache",
"stackTrace",
"securityInfo",
];
return fetchNetworkUpdatePacket(connector.requestData, resource, updateTypes);
}
/**
* Search through all data within the specified resource.
*/
function searchResource(resource, query) {
return async ({ dispatch, getState }) => {
const state = getState();
const ongoingSearch = getOngoingSearch(state);
const modifiers = {
caseSensitive: state.search.caseSensitive,
};
// Run search in a worker and wait for the results. The return
// value is an array with search occurrences.
const result = await searchInResource(resource, query, modifiers);
if (!result.length || ongoingSearch.isCanceled()) {
return;
}
dispatch(addSearchResult(resource, result));
};
}
/**
* Add search query to the reducer.
*/
function addSearchResult(resource, result) {
return {
type: ADD_SEARCH_RESULT,
resource,
result,
};
}
/**
* Add search query to the reducer.
*/
function addSearchQuery(query) {
return {
type: ADD_SEARCH_QUERY,
query,
};
}
/**
* Clear all search results.
*/
function clearSearchResults() {
return {
type: CLEAR_SEARCH_RESULTS,
};
}
/**
* Used to clear and cancel an ongoing search.
* @returns {Function}
*/
function clearSearchResultAndCancel() {
return ({ dispatch }) => {
dispatch(stopOngoingSearch());
dispatch(clearSearchResults());
};
}
/**
* Update status of the current search.
*/
function updateSearchStatus(status) {
return {
type: UPDATE_SEARCH_STATUS,
status,
};
}
/**
* Close the entire search panel.
*/
function closeSearch() {
return ({ dispatch }) => {
dispatch(stopOngoingSearch());
dispatch({ type: OPEN_ACTION_BAR, open: false });
};
}
/**
* Open the entire search panel
* @returns {Function}
*/
function openSearch() {
return ({ dispatch }) => {
dispatch({ type: OPEN_ACTION_BAR, open: true });
dispatch({
type: SELECT_ACTION_BAR_TAB,
id: PANELS.SEARCH,
});
};
}
/**
* Toggles case sensitive search
* @returns {Function}
*/
function toggleCaseSensitiveSearch() {
return ({ dispatch }) => {
dispatch({ type: TOGGLE_SEARCH_CASE_SENSITIVE_SEARCH });
};
}
/**
* Toggle visibility of search panel in network panel
*/
function toggleSearchPanel() {
return ({ dispatch, getState }) => {
const state = getState();
state.ui.networkActionOpen &&
state.ui.selectedActionBarTabId === PANELS.SEARCH
? dispatch({ type: OPEN_ACTION_BAR, open: false })
: dispatch({ type: OPEN_ACTION_BAR, open: true });
dispatch({
type: SELECT_ACTION_BAR_TAB,
id: PANELS.SEARCH,
});
};
}
/**
* Append new search object into the reducer. The search object
* is cancellable and so, it implements `cancel` method.
*/
function addOngoingSearch(ongoingSearch) {
return {
type: ADD_ONGOING_SEARCH,
ongoingSearch,
};
}
/**
* Cancel the current ongoing search.
*/
function stopOngoingSearch() {
return ({ dispatch, getState }) => {
const state = getState();
const ongoingSearch = getOngoingSearch(state);
const status = getSearchStatus(state);
if (ongoingSearch && status !== SEARCH_STATUS.DONE) {
ongoingSearch.cancel();
dispatch(updateSearchStatus(SEARCH_STATUS.CANCELED));
}
};
}
/**
* This action is fired when the user selects a search result
* within the Search panel. It opens the details side bar and
* selects the right side panel to show the context of the
* clicked search result.
*/
function navigate(searchResult) {
return ({ dispatch }) => {
// Store target search result in Search reducer. It's used
// for search result navigation within the side panels.
dispatch(setTargetSearchResult(searchResult));
// Preselect the right side panel.
dispatch(selectDetailsPanelTab(searchResult.panel));
// Select related request in the UI (it also opens the
// right side bar automatically).
dispatch(selectRequest(searchResult.parentResource.id));
};
}
function setTargetSearchResult(searchResult) {
return {
type: SET_TARGET_SEARCH_RESULT,
searchResult,
};
}
module.exports = {
search,
closeSearch,
openSearch,
clearSearchResults,
addSearchQuery,
toggleSearchPanel,
navigate,
setTargetSearchResult,
toggleCaseSensitiveSearch,
clearSearchResultAndCancel,
stopOngoingSearch,
};