summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/src/actions
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/client/netmonitor/src/actions
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/netmonitor/src/actions')
-rw-r--r--devtools/client/netmonitor/src/actions/batching.js50
-rw-r--r--devtools/client/netmonitor/src/actions/filters.js58
-rw-r--r--devtools/client/netmonitor/src/actions/http-custom-request.js128
-rw-r--r--devtools/client/netmonitor/src/actions/index.js32
-rw-r--r--devtools/client/netmonitor/src/actions/messages.js188
-rw-r--r--devtools/client/netmonitor/src/actions/moz.build18
-rw-r--r--devtools/client/netmonitor/src/actions/request-blocking.js155
-rw-r--r--devtools/client/netmonitor/src/actions/requests.js183
-rw-r--r--devtools/client/netmonitor/src/actions/search.js316
-rw-r--r--devtools/client/netmonitor/src/actions/selection.js80
-rw-r--r--devtools/client/netmonitor/src/actions/sort.js20
-rw-r--r--devtools/client/netmonitor/src/actions/timing-markers.js22
-rw-r--r--devtools/client/netmonitor/src/actions/ui.js257
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,
+};