diff options
Diffstat (limited to 'devtools/client/netmonitor/src/actions')
-rw-r--r-- | devtools/client/netmonitor/src/actions/batching.js | 50 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/filters.js | 58 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/http-custom-request.js | 128 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/index.js | 32 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/messages.js | 188 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/moz.build | 18 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/request-blocking.js | 155 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/requests.js | 183 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/search.js | 316 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/selection.js | 80 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/sort.js | 20 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/timing-markers.js | 22 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/actions/ui.js | 257 |
13 files changed, 1507 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/actions/batching.js b/devtools/client/netmonitor/src/actions/batching.js new file mode 100644 index 0000000000..f3b84f950d --- /dev/null +++ b/devtools/client/netmonitor/src/actions/batching.js @@ -0,0 +1,50 @@ +/* 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 { + BATCH_ACTIONS, + BATCH_ENABLE, + BATCH_RESET, + BATCH_FLUSH, +} = require("resource://devtools/client/netmonitor/src/constants.js"); + +/** + * Process multiple actions at once as part of one dispatch, and produce only one + * state update at the end. This action is not processed by any reducer, but by a + * special store enhancer. + */ +function batchActions(actions) { + return { + type: BATCH_ACTIONS, + actions, + }; +} + +function batchEnable(enabled) { + return { + type: BATCH_ENABLE, + enabled, + }; +} + +function batchReset() { + return { + type: BATCH_RESET, + }; +} + +function batchFlush() { + return { + type: BATCH_FLUSH, + }; +} + +module.exports = { + batchActions, + batchEnable, + batchReset, + batchFlush, +}; diff --git a/devtools/client/netmonitor/src/actions/filters.js b/devtools/client/netmonitor/src/actions/filters.js new file mode 100644 index 0000000000..5aa5a99c10 --- /dev/null +++ b/devtools/client/netmonitor/src/actions/filters.js @@ -0,0 +1,58 @@ +/* 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 { + ENABLE_REQUEST_FILTER_TYPE_ONLY, + TOGGLE_REQUEST_FILTER_TYPE, + SET_REQUEST_FILTER_TEXT, +} = require("resource://devtools/client/netmonitor/src/constants.js"); + +/** + * Toggle an existing filter type state. + * If type 'all' is specified, all the other filter types are set to false. + * Available filter types are defined in filters reducer. + * + * @param {string} filter - A filter type is going to be updated + */ +function toggleRequestFilterType(filter) { + return { + type: TOGGLE_REQUEST_FILTER_TYPE, + filter, + }; +} + +/** + * Enable filter type exclusively. + * Except filter type is set to true, all the other filter types are set + * to false. + * Available filter types are defined in filters reducer. + * + * @param {string} filter - A filter type is going to be updated + */ +function enableRequestFilterTypeOnly(filter) { + return { + type: ENABLE_REQUEST_FILTER_TYPE_ONLY, + filter, + }; +} + +/** + * Set filter text in toolbar. + * + * @param {string} text - A filter text is going to be set + */ +function setRequestFilterText(text) { + return { + type: SET_REQUEST_FILTER_TEXT, + text, + }; +} + +module.exports = { + enableRequestFilterTypeOnly, + toggleRequestFilterType, + setRequestFilterText, +}; diff --git a/devtools/client/netmonitor/src/actions/http-custom-request.js b/devtools/client/netmonitor/src/actions/http-custom-request.js new file mode 100644 index 0000000000..e045107410 --- /dev/null +++ b/devtools/client/netmonitor/src/actions/http-custom-request.js @@ -0,0 +1,128 @@ +/* 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 { + OPEN_ACTION_BAR, + SELECT_ACTION_BAR_TAB, + PANELS, + RIGHT_CLICK_REQUEST, + PRESELECT_REQUEST, +} = require("resource://devtools/client/netmonitor/src/constants.js"); + +const { + selectRequest, +} = require("resource://devtools/client/netmonitor/src/actions/selection.js"); + +const { + openNetworkDetails, +} = require("resource://devtools/client/netmonitor/src/actions/ui.js"); + +const { + getRequestById, + getRequestByChannelId, +} = require("resource://devtools/client/netmonitor/src/selectors/index.js"); + +const { + fetchNetworkUpdatePacket, +} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); + +/** + * Open the entire HTTP Custom Request panel + * @returns {Function} + */ +function openHTTPCustomRequest(isOpen) { + return ({ dispatch, getState }) => { + dispatch({ type: OPEN_ACTION_BAR, open: isOpen }); + + dispatch({ + type: SELECT_ACTION_BAR_TAB, + id: PANELS.HTTP_CUSTOM_REQUEST, + }); + }; +} + +/** + * Toggle visibility of New Custom Request panel in network panel + */ +function toggleHTTPCustomRequestPanel() { + return ({ dispatch, getState }) => { + const state = getState(); + + const shouldClose = + state.ui.networkActionOpen && + state.ui.selectedActionBarTabId === PANELS.HTTP_CUSTOM_REQUEST; + dispatch({ type: OPEN_ACTION_BAR, open: !shouldClose }); + + // reset the right clicked request + dispatch({ type: RIGHT_CLICK_REQUEST, id: null }); + + dispatch({ + type: SELECT_ACTION_BAR_TAB, + id: PANELS.HTTP_CUSTOM_REQUEST, + }); + }; +} + +/** + * Send a new HTTP request using the data in the custom request form. + */ +function sendHTTPCustomRequest(request) { + return async ({ dispatch, getState, connector, commands }) => { + if (!request) { + return; + } + + // Fetch request headers and post data from the backend, if needed. + // This is only needed if we are resending a request without editing. + + if (request.requestHeadersAvailable || request.requestPostDataAvailable) { + await fetchNetworkUpdatePacket(connector.requestData, request, [ + "requestHeaders", + "requestPostData", + ]); + + // Get the request again, to get all the updated data + request = getRequestById(getState(), request.id); + } + + // Send a new HTTP request using the data in the custom request form + const data = { + cause: request.cause || {}, + url: request.url, + method: request.method, + httpVersion: request.httpVersion, + }; + + if (request.requestHeaders) { + data.headers = request.requestHeaders.headers; + } + + if (request.requestPostData) { + data.body = request.requestPostData.postData?.text; + } + + const { channelId } = await commands.networkCommand.sendHTTPRequest(data); + + const newRequest = getRequestByChannelId(getState(), channelId); + // If the new custom request is available already select the request, else + // preselect the request. + if (newRequest) { + await dispatch(selectRequest(newRequest.id)); + } else { + await dispatch({ + type: PRESELECT_REQUEST, + id: channelId, + }); + } + dispatch(openNetworkDetails(true)); + }; +} + +module.exports = { + openHTTPCustomRequest, + toggleHTTPCustomRequestPanel, + sendHTTPCustomRequest, +}; diff --git a/devtools/client/netmonitor/src/actions/index.js b/devtools/client/netmonitor/src/actions/index.js new file mode 100644 index 0000000000..6e28281ab7 --- /dev/null +++ b/devtools/client/netmonitor/src/actions/index.js @@ -0,0 +1,32 @@ +/* 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 batching = require("resource://devtools/client/netmonitor/src/actions/batching.js"); +const filters = require("resource://devtools/client/netmonitor/src/actions/filters.js"); +const httpCustomRequest = require("resource://devtools/client/netmonitor/src/actions/http-custom-request.js"); +const requests = require("resource://devtools/client/netmonitor/src/actions/requests.js"); +const selection = require("resource://devtools/client/netmonitor/src/actions/selection.js"); +const sort = require("resource://devtools/client/netmonitor/src/actions/sort.js"); +const timingMarkers = require("resource://devtools/client/netmonitor/src/actions/timing-markers.js"); +const ui = require("resource://devtools/client/netmonitor/src/actions/ui.js"); +const messages = require("resource://devtools/client/netmonitor/src/actions/messages.js"); +const search = require("resource://devtools/client/netmonitor/src/actions/search.js"); +const requestBlocking = require("resource://devtools/client/netmonitor/src/actions/request-blocking.js"); + +Object.assign( + exports, + batching, + filters, + httpCustomRequest, + requests, + search, + selection, + sort, + timingMarkers, + ui, + messages, + requestBlocking +); diff --git a/devtools/client/netmonitor/src/actions/messages.js b/devtools/client/netmonitor/src/actions/messages.js new file mode 100644 index 0000000000..a8f49e9735 --- /dev/null +++ b/devtools/client/netmonitor/src/actions/messages.js @@ -0,0 +1,188 @@ +/* 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 { + MSG_ADD, + MSG_SELECT, + MSG_OPEN_DETAILS, + MSG_CLEAR, + MSG_TOGGLE_FILTER_TYPE, + MSG_TOGGLE_CONTROL, + MSG_SET_FILTER_TEXT, + MSG_TOGGLE_COLUMN, + MSG_RESET_COLUMNS, + MSG_CLOSE_CONNECTION, +} = require("resource://devtools/client/netmonitor/src/constants.js"); + +const { + getDisplayedMessages, +} = require("resource://devtools/client/netmonitor/src/selectors/index.js"); +const PAGE_SIZE_ITEM_COUNT_RATIO = 5; + +/** + * Add message into state. + */ +function addMessage(httpChannelId, data, batch) { + return { + type: MSG_ADD, + httpChannelId, + data, + meta: { batch }, + }; +} + +/** + * Select message. + */ +function selectMessage(message) { + return { + type: MSG_SELECT, + open: true, + message, + }; +} + +/** + * Open message details panel. + * + * @param {boolean} open - expected message details panel open state + */ +function openMessageDetails(open) { + return { + type: MSG_OPEN_DETAILS, + open, + }; +} + +/** + * Clear all messages from the MessageListContent + * component belonging to the current channelId + */ +function clearMessages() { + return { + type: MSG_CLEAR, + }; +} + +/** + * Show filtered messages from the MessageListContent + * component belonging to the current channelId + */ +function toggleMessageFilterType(filter) { + return { + type: MSG_TOGGLE_FILTER_TYPE, + filter, + }; +} + +/** + * Show control frames from the MessageListContent + * component belonging to the current channelId + */ +function toggleControlFrames() { + return { + type: MSG_TOGGLE_CONTROL, + }; +} + +/** + * Set filter text in toolbar. + * + */ +function setMessageFilterText(text) { + return { + type: MSG_SET_FILTER_TEXT, + text, + }; +} + +/** + * Resets all Messages columns to their default state. + * + */ +function resetMessageColumns() { + return { + type: MSG_RESET_COLUMNS, + }; +} + +/** + * Toggles a Message column + * + * @param {string} column - The column that is going to be toggled + */ +function toggleMessageColumn(column) { + return { + type: MSG_TOGGLE_COLUMN, + column, + }; +} + +/** + * Sets current connection status to closed + * + * @param {number} httpChannelId - Unique id identifying the channel + * @param {boolean} wasClean - False if connection terminated due to error + * @param {number} code - Error code + * @param {string} reason + */ +function closeConnection(httpChannelId, wasClean, code, reason) { + return { + type: MSG_CLOSE_CONNECTION, + httpChannelId, + wasClean, + code, + reason, + }; +} + +/** + * Move the selection up to down according to the "delta" parameter. Possible values: + * - Number: positive or negative, move up or down by specified distance + * - "PAGE_UP" | "PAGE_DOWN" (String): page up or page down + * - +Infinity | -Infinity: move to the start or end of the list + */ +function selectMessageDelta(delta) { + return ({ dispatch, getState }) => { + const state = getState(); + const messages = getDisplayedMessages(state); + + if (messages.length === 0) { + return; + } + + const selIndex = messages.findIndex( + r => r === state.messages.selectedMessage + ); + + if (delta === "PAGE_DOWN") { + delta = Math.ceil(messages.length / PAGE_SIZE_ITEM_COUNT_RATIO); + } else if (delta === "PAGE_UP") { + delta = -Math.ceil(messages.length / PAGE_SIZE_ITEM_COUNT_RATIO); + } + + const newIndex = Math.min( + Math.max(0, selIndex + delta), + messages.length - 1 + ); + const newItem = messages[newIndex]; + dispatch(selectMessage(newItem)); + }; +} + +module.exports = { + addMessage, + clearMessages, + closeConnection, + openMessageDetails, + resetMessageColumns, + selectMessage, + selectMessageDelta, + setMessageFilterText, + toggleControlFrames, + toggleMessageColumn, + toggleMessageFilterType, +}; diff --git a/devtools/client/netmonitor/src/actions/moz.build b/devtools/client/netmonitor/src/actions/moz.build new file mode 100644 index 0000000000..fc74e8cc56 --- /dev/null +++ b/devtools/client/netmonitor/src/actions/moz.build @@ -0,0 +1,18 @@ +# 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/. + +DevToolsModules( + "batching.js", + "filters.js", + "http-custom-request.js", + "index.js", + "messages.js", + "request-blocking.js", + "requests.js", + "search.js", + "selection.js", + "sort.js", + "timing-markers.js", + "ui.js", +) diff --git a/devtools/client/netmonitor/src/actions/request-blocking.js b/devtools/client/netmonitor/src/actions/request-blocking.js new file mode 100644 index 0000000000..502999c79f --- /dev/null +++ b/devtools/client/netmonitor/src/actions/request-blocking.js @@ -0,0 +1,155 @@ +/* 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_BLOCKED_URL, + TOGGLE_BLOCKING_ENABLED, + TOGGLE_BLOCKED_URL, + UPDATE_BLOCKED_URL, + REMOVE_BLOCKED_URL, + REMOVE_ALL_BLOCKED_URLS, + ENABLE_ALL_BLOCKED_URLS, + DISABLE_ALL_BLOCKED_URLS, + DISABLE_MATCHING_URLS, + SYNCED_BLOCKED_URLS, + OPEN_ACTION_BAR, + SELECT_ACTION_BAR_TAB, + PANELS, +} = require("resource://devtools/client/netmonitor/src/constants.js"); + +function toggleRequestBlockingPanel() { + return async ({ dispatch, getState }) => { + const state = getState(); + if ( + state.ui.networkActionOpen && + state.ui.selectedActionBarTabId === PANELS.BLOCKING + ) { + dispatch(closeRequestBlocking()); + } else { + dispatch(await openRequestBlocking()); + } + }; +} + +function toggleBlockingEnabled(enabled) { + return { + type: TOGGLE_BLOCKING_ENABLED, + enabled, + }; +} + +function removeBlockedUrl(url) { + return { + type: REMOVE_BLOCKED_URL, + url, + }; +} + +function removeAllBlockedUrls() { + return { type: REMOVE_ALL_BLOCKED_URLS }; +} + +function enableAllBlockedUrls() { + return { type: ENABLE_ALL_BLOCKED_URLS }; +} + +function disableAllBlockedUrls() { + return { type: DISABLE_ALL_BLOCKED_URLS }; +} + +function addBlockedUrl(url) { + return { + type: ADD_BLOCKED_URL, + url, + }; +} + +function toggleBlockedUrl(url) { + return { + type: TOGGLE_BLOCKED_URL, + url, + }; +} + +function updateBlockedUrl(oldUrl, newUrl) { + return { + type: UPDATE_BLOCKED_URL, + oldUrl, + newUrl, + }; +} + +async function openRequestBlocking() { + return async ({ dispatch, getState, commands }) => { + const state = getState(); + if (!state.requestBlocking.blockingSynced) { + const blockedUrls = state.requestBlocking.blockedUrls; + const responses = await commands.networkCommand.getBlockedUrls(); + const urls = responses.flat(); + if (urls.length !== blockedUrls.length) { + urls.forEach(url => dispatch(addBlockedUrl(url))); + } + dispatch({ type: SYNCED_BLOCKED_URLS, synced: true }); + } + + dispatch({ type: OPEN_ACTION_BAR, open: true }); + dispatch({ + type: SELECT_ACTION_BAR_TAB, + id: PANELS.BLOCKING, + }); + }; +} + +function closeRequestBlocking() { + return ({ dispatch }) => { + dispatch({ type: OPEN_ACTION_BAR, open: false }); + dispatch({ + type: SELECT_ACTION_BAR_TAB, + id: PANELS.BLOCKING, + }); + }; +} + +function openRequestBlockingAndAddUrl(url) { + return async ({ dispatch, getState }) => { + const showBlockingPanel = Services.prefs.getBoolPref( + "devtools.netmonitor.features.requestBlocking" + ); + + if (showBlockingPanel) { + dispatch(await openRequestBlocking()); + } + dispatch({ type: ADD_BLOCKED_URL, url }); + }; +} + +function openRequestBlockingAndDisableUrls(url) { + return async ({ dispatch, getState }) => { + const showBlockingPanel = Services.prefs.getBoolPref( + "devtools.netmonitor.features.requestBlocking" + ); + + if (showBlockingPanel) { + dispatch(await openRequestBlocking()); + } + + dispatch({ type: DISABLE_MATCHING_URLS, url }); + }; +} + +module.exports = { + toggleRequestBlockingPanel, + addBlockedUrl, + toggleBlockingEnabled, + toggleBlockedUrl, + removeBlockedUrl, + removeAllBlockedUrls, + enableAllBlockedUrls, + disableAllBlockedUrls, + updateBlockedUrl, + openRequestBlockingAndAddUrl, + openRequestBlockingAndDisableUrls, +}; diff --git a/devtools/client/netmonitor/src/actions/requests.js b/devtools/client/netmonitor/src/actions/requests.js new file mode 100644 index 0000000000..838f2509a9 --- /dev/null +++ b/devtools/client/netmonitor/src/actions/requests.js @@ -0,0 +1,183 @@ +/* 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_REQUEST, + CLEAR_REQUESTS, + CLONE_REQUEST, + CLONE_SELECTED_REQUEST, + REMOVE_SELECTED_CUSTOM_REQUEST, + RIGHT_CLICK_REQUEST, + SEND_CUSTOM_REQUEST, + SET_EVENT_STREAM_FLAG, + SET_RECORDING_STATE, + UPDATE_REQUEST, +} = require("resource://devtools/client/netmonitor/src/constants.js"); +const { + getSelectedRequest, + getRequestById, + getRecordingState, +} = require("resource://devtools/client/netmonitor/src/selectors/index.js"); +const { + fetchNetworkUpdatePacket, +} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); + +function addRequest(id, data, batch) { + return { + type: ADD_REQUEST, + id, + data, + meta: { batch }, + }; +} + +function updateRequest(id, data, batch) { + return { + type: UPDATE_REQUEST, + id, + data, + meta: { batch }, + }; +} + +function setEventStreamFlag(id, batch) { + return { + type: SET_EVENT_STREAM_FLAG, + id, + meta: { batch }, + }; +} + +/** + * Clone request by id. Used when cloning a request + * through the "Edit and Resend" option present in the context menu. + */ +function cloneRequest(id) { + return { + id, + type: CLONE_REQUEST, + }; +} + +/** + * Right click a request without selecting it. + */ +function rightClickRequest(id) { + return { + id, + type: RIGHT_CLICK_REQUEST, + }; +} + +/** + * Clone the currently selected request, set the "isCustom" attribute. + * Used by the "Edit and Resend" feature. + */ +function cloneSelectedRequest() { + return { + type: CLONE_SELECTED_REQUEST, + }; +} + +/** + * Send a new HTTP request using the data in the custom request form. + */ +function sendCustomRequest(requestId = null) { + return async ({ dispatch, getState, connector, commands }) => { + let request; + if (requestId) { + request = getRequestById(getState(), requestId); + } else { + request = getSelectedRequest(getState()); + } + + if (!request) { + return; + } + + // Fetch request headers and post data from the backend. + await fetchNetworkUpdatePacket(connector.requestData, request, [ + "requestHeaders", + "requestPostData", + ]); + + // Reload the request from the store to get the headers. + request = getRequestById(getState(), request.id); + + // Send a new HTTP request using the data in the custom request form + const data = { + cause: request.cause, + url: request.url, + method: request.method, + httpVersion: request.httpVersion, + }; + + if (request.requestHeaders) { + data.headers = request.requestHeaders.headers; + } + + if (request.requestPostData) { + data.body = request.requestPostData.postData.text; + } + + const { channelId } = await commands.networkCommand.sendHTTPRequest(data); + + dispatch({ + type: SEND_CUSTOM_REQUEST, + id: channelId, + }); + }; +} + +/** + * Remove a request from the list. Supports removing only cloned requests with a + * "isCustom" attribute. Other requests never need to be removed. + */ +function removeSelectedCustomRequest() { + return { + type: REMOVE_SELECTED_CUSTOM_REQUEST, + }; +} +/** + * Clear all requests + */ +function clearRequests() { + return ({ dispatch, connector }) => { + dispatch({ type: CLEAR_REQUESTS }); + connector.clear(); + }; +} + +/** + * Toggle monitoring + */ +function toggleRecording() { + return async ({ dispatch, getState, connector }) => { + const recording = !getRecordingState(getState()); + if (recording) { + await connector.resume(); + } else { + connector.pause(); + } + dispatch({ + type: SET_RECORDING_STATE, + recording, + }); + }; +} + +module.exports = { + addRequest, + clearRequests, + cloneRequest, + cloneSelectedRequest, + rightClickRequest, + removeSelectedCustomRequest, + sendCustomRequest, + setEventStreamFlag, + toggleRecording, + updateRequest, +}; diff --git a/devtools/client/netmonitor/src/actions/search.js b/devtools/client/netmonitor/src/actions/search.js new file mode 100644 index 0000000000..97b123d361 --- /dev/null +++ b/devtools/client/netmonitor/src/actions/search.js @@ -0,0 +1,316 @@ +/* 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, getState }) => { + 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, getState }) => { + dispatch(stopOngoingSearch()); + dispatch({ type: OPEN_ACTION_BAR, open: false }); + }; +} + +/** + * Open the entire search panel + * @returns {Function} + */ +function openSearch() { + return ({ dispatch, getState }) => { + 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, getState }) => { + 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, getState }) => { + // 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, +}; diff --git a/devtools/client/netmonitor/src/actions/selection.js b/devtools/client/netmonitor/src/actions/selection.js new file mode 100644 index 0000000000..4603fcb61c --- /dev/null +++ b/devtools/client/netmonitor/src/actions/selection.js @@ -0,0 +1,80 @@ +/* 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 { + SELECT_REQUEST, +} = require("resource://devtools/client/netmonitor/src/constants.js"); +const { + getDisplayedRequests, + getSortedRequests, +} = require("resource://devtools/client/netmonitor/src/selectors/index.js"); + +const PAGE_SIZE_ITEM_COUNT_RATIO = 5; + +/** + * Select request with a given id. + */ +function selectRequest(id, request) { + return { + type: SELECT_REQUEST, + id, + request, + }; +} + +/** + * Select request with a given index (sorted order) + */ +function selectRequestByIndex(index) { + return ({ dispatch, getState }) => { + const requests = getSortedRequests(getState()); + let itemId; + if (index >= 0 && index < requests.length) { + itemId = requests[index].id; + } + dispatch(selectRequest(itemId)); + }; +} + +/** + * Move the selection up to down according to the "delta" parameter. Possible values: + * - Number: positive or negative, move up or down by specified distance + * - "PAGE_UP" | "PAGE_DOWN" (String): page up or page down + * - +Infinity | -Infinity: move to the start or end of the list + */ +function selectDelta(delta) { + return ({ dispatch, getState }) => { + const state = getState(); + const requests = getDisplayedRequests(state); + + if (!requests.length) { + return; + } + + const selIndex = requests.findIndex( + r => r.id === state.requests.selectedId + ); + + if (delta === "PAGE_DOWN") { + delta = Math.ceil(requests.length / PAGE_SIZE_ITEM_COUNT_RATIO); + } else if (delta === "PAGE_UP") { + delta = -Math.ceil(requests.length / PAGE_SIZE_ITEM_COUNT_RATIO); + } + + const newIndex = Math.min( + Math.max(0, selIndex + delta), + requests.length - 1 + ); + const newItem = requests[newIndex]; + dispatch(selectRequest(newItem.id, newItem)); + }; +} + +module.exports = { + selectRequest, + selectRequestByIndex, + selectDelta, +}; diff --git a/devtools/client/netmonitor/src/actions/sort.js b/devtools/client/netmonitor/src/actions/sort.js new file mode 100644 index 0000000000..06c3f92c54 --- /dev/null +++ b/devtools/client/netmonitor/src/actions/sort.js @@ -0,0 +1,20 @@ +/* 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 { + SORT_BY, +} = require("resource://devtools/client/netmonitor/src/constants.js"); + +function sortBy(sortType) { + return { + type: SORT_BY, + sortType, + }; +} + +module.exports = { + sortBy, +}; diff --git a/devtools/client/netmonitor/src/actions/timing-markers.js b/devtools/client/netmonitor/src/actions/timing-markers.js new file mode 100644 index 0000000000..cce53e8100 --- /dev/null +++ b/devtools/client/netmonitor/src/actions/timing-markers.js @@ -0,0 +1,22 @@ +/* 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_TIMING_MARKER, + CLEAR_TIMING_MARKERS, +} = require("resource://devtools/client/netmonitor/src/constants.js"); + +exports.addTimingMarker = marker => { + return { + type: ADD_TIMING_MARKER, + marker, + }; +}; + +exports.clearTimingMarkers = () => { + return { + type: CLEAR_TIMING_MARKERS, + }; +}; diff --git a/devtools/client/netmonitor/src/actions/ui.js b/devtools/client/netmonitor/src/actions/ui.js new file mode 100644 index 0000000000..4be087c5df --- /dev/null +++ b/devtools/client/netmonitor/src/actions/ui.js @@ -0,0 +1,257 @@ +/* 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 { + ACTIVITY_TYPE, + OPEN_NETWORK_DETAILS, + RESIZE_NETWORK_DETAILS, + ENABLE_PERSISTENT_LOGS, + DISABLE_BROWSER_CACHE, + OPEN_STATISTICS, + RESET_COLUMNS, + SELECT_DETAILS_PANEL_TAB, + SELECT_ACTION_BAR_TAB, + TOGGLE_COLUMN, + WATERFALL_RESIZE, + SET_COLUMNS_WIDTH, + SET_HEADERS_URL_PREVIEW_EXPANDED, + OPEN_ACTION_BAR, +} = require("resource://devtools/client/netmonitor/src/constants.js"); + +const { + getDisplayedRequests, +} = require("resource://devtools/client/netmonitor/src/selectors/index.js"); + +const DEVTOOLS_DISABLE_CACHE_PREF = "devtools.cache.disabled"; + +/** + * Change network details panel. + * + * @param {boolean} open - expected network details panel open state + */ +function openNetworkDetails(open) { + return ({ dispatch, getState }) => { + const visibleRequestItems = getDisplayedRequests(getState()); + const defaultSelectedId = visibleRequestItems.length + ? visibleRequestItems[0].id + : null; + + return dispatch({ + type: OPEN_NETWORK_DETAILS, + open, + defaultSelectedId, + }); + }; +} + +/** + * Change network action bar open state. + * + * @param {boolean} open - expected network action bar open state + */ +function openNetworkActionBar(open) { + return { + type: OPEN_ACTION_BAR, + open, + }; +} + +/** + * Change network details panel size. + * + * @param {integer} width + * @param {integer} height + */ +function resizeNetworkDetails(width, height) { + return { + type: RESIZE_NETWORK_DETAILS, + width, + height, + }; +} + +/** + * Change persistent logs state. + * + * @param {boolean} enabled - expected persistent logs enabled state + */ +function enablePersistentLogs(enabled, skipTelemetry = false) { + return { + type: ENABLE_PERSISTENT_LOGS, + enabled, + skipTelemetry, + }; +} + +/** + * Change browser cache state. + * + * @param {boolean} disabled - expected browser cache in disable state + */ +function disableBrowserCache(disabled) { + return { + type: DISABLE_BROWSER_CACHE, + disabled, + }; +} + +/** + * Change performance statistics panel open state. + * + * @param {Object} connector - connector object to the backend + * @param {boolean} visible - expected performance statistics panel open state + */ +function openStatistics(connector, open) { + if (open) { + connector.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED); + } else if (Services.prefs.getBoolPref(DEVTOOLS_DISABLE_CACHE_PREF)) { + // Opening the Statistics panel reconfigures the page and enables + // the browser cache (using ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED). + // So, make sure to disable the cache again when the user returns back + // from the Statistics panel (if DEVTOOLS_DISABLE_CACHE_PREF == true). + // See also bug 1430359. + connector.triggerActivity(ACTIVITY_TYPE.DISABLE_CACHE); + } + return { + type: OPEN_STATISTICS, + open, + }; +} + +/** + * Resets all columns to their default state. + * + */ +function resetColumns() { + return { + type: RESET_COLUMNS, + }; +} + +/** + * Waterfall width has changed (likely on window resize). Update the UI. + */ +function resizeWaterfall(width) { + return { + type: WATERFALL_RESIZE, + width, + }; +} + +/** + * Change the selected tab for network details panel. + * + * @param {string} id - tab id to be selected + */ +function selectDetailsPanelTab(id) { + return { + type: SELECT_DETAILS_PANEL_TAB, + id, + }; +} + +/** + * Change the selected tab for network action bar. + * + * @param {string} id - tab id to be selected + */ +function selectActionBarTab(id) { + return { + type: SELECT_ACTION_BAR_TAB, + id, + }; +} + +/** + * Toggles a column + * + * @param {string} column - The column that is going to be toggled + */ +function toggleColumn(column) { + return { + type: TOGGLE_COLUMN, + column, + }; +} + +/** + * Set width of multiple columns + * + * @param {array} widths - array of pairs {name, width} + */ +function setColumnsWidth(widths) { + return { + type: SET_COLUMNS_WIDTH, + widths, + }; +} + +/** + * Toggle network details panel. + */ +function toggleNetworkDetails() { + return ({ dispatch, getState }) => + dispatch(openNetworkDetails(!getState().ui.networkDetailsOpen)); +} + +/** + * Toggle network action panel. + */ +function toggleNetworkActionBar() { + return ({ dispatch, getState }) => + dispatch(openNetworkActionBar(!getState().ui.networkActionOpen)); +} + +/** + * Toggle persistent logs status. + */ +function togglePersistentLogs() { + return ({ dispatch, getState }) => + dispatch(enablePersistentLogs(!getState().ui.persistentLogsEnabled)); +} + +/** + * Toggle browser cache status. + */ +function toggleBrowserCache() { + return ({ dispatch, getState }) => + dispatch(disableBrowserCache(!getState().ui.browserCacheDisabled)); +} + +/** + * Toggle performance statistics panel. + */ +function toggleStatistics(connector) { + return ({ dispatch, getState }) => + dispatch(openStatistics(connector, !getState().ui.statisticsOpen)); +} + +function setHeadersUrlPreviewExpanded(expanded) { + return { + type: SET_HEADERS_URL_PREVIEW_EXPANDED, + expanded, + }; +} + +module.exports = { + openNetworkDetails, + openNetworkActionBar, + resizeNetworkDetails, + enablePersistentLogs, + disableBrowserCache, + openStatistics, + resetColumns, + resizeWaterfall, + selectDetailsPanelTab, + selectActionBarTab, + toggleColumn, + setColumnsWidth, + toggleNetworkDetails, + toggleNetworkActionBar, + togglePersistentLogs, + toggleBrowserCache, + toggleStatistics, + setHeadersUrlPreviewExpanded, +}; |