summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/src/middleware
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/netmonitor/src/middleware')
-rw-r--r--devtools/client/netmonitor/src/middleware/batching.js146
-rw-r--r--devtools/client/netmonitor/src/middleware/event-telemetry.js192
-rw-r--r--devtools/client/netmonitor/src/middleware/moz.build11
-rw-r--r--devtools/client/netmonitor/src/middleware/prefs.js116
-rw-r--r--devtools/client/netmonitor/src/middleware/request-blocking.js58
-rw-r--r--devtools/client/netmonitor/src/middleware/throttling.js26
6 files changed, 549 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/middleware/batching.js b/devtools/client/netmonitor/src/middleware/batching.js
new file mode 100644
index 0000000000..9d8c57084c
--- /dev/null
+++ b/devtools/client/netmonitor/src/middleware/batching.js
@@ -0,0 +1,146 @@
+/* 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");
+
+const REQUESTS_REFRESH_RATE = 50; // ms
+
+/**
+ * Middleware that watches for actions with a "batch = true" value in their meta field.
+ * These actions are queued and dispatched as one batch after a timeout.
+ * Special actions that are handled by this middleware:
+ * - BATCH_ENABLE can be used to enable and disable the batching.
+ * - BATCH_RESET discards the actions that are currently in the queue.
+ */
+function batchingMiddleware(store) {
+ return next => {
+ let queuedActions = [];
+ let enabled = true;
+ let flushTask = null;
+
+ return action => {
+ if (action.type === BATCH_ENABLE) {
+ return setEnabled(action.enabled);
+ }
+
+ if (action.type === BATCH_RESET) {
+ return resetQueue();
+ }
+
+ if (action.type === BATCH_FLUSH) {
+ return flushQueue();
+ }
+
+ if (action.meta?.batch) {
+ if (!enabled) {
+ next(action);
+ return Promise.resolve();
+ }
+
+ queuedActions.push(action);
+
+ if (!flushTask) {
+ flushTask = new DelayedTask(flushActions, REQUESTS_REFRESH_RATE);
+ }
+
+ return flushTask.promise;
+ }
+
+ return next(action);
+ };
+
+ function setEnabled(value) {
+ enabled = value;
+
+ // If disabling the batching, flush the actions that have been queued so far
+ if (!enabled && flushTask) {
+ flushTask.runNow();
+ }
+ }
+
+ function resetQueue() {
+ queuedActions = [];
+
+ if (flushTask) {
+ flushTask.cancel();
+ flushTask = null;
+ }
+ }
+
+ function flushQueue() {
+ if (flushTask) {
+ flushTask.runNow();
+ }
+ }
+
+ function flushActions() {
+ const actions = queuedActions;
+ queuedActions = [];
+
+ next({
+ type: BATCH_ACTIONS,
+ actions,
+ });
+
+ flushTask = null;
+ }
+ };
+}
+
+/**
+ * Create a delayed task that calls the specified task function after a delay.
+ */
+function DelayedTask(taskFn, delay) {
+ this._promise = new Promise((resolve, reject) => {
+ this.runTask = cancel => {
+ if (cancel) {
+ reject("Task cancelled");
+ } else {
+ taskFn();
+ resolve();
+ }
+ this.runTask = null;
+ };
+ this.timeout = setTimeout(this.runTask, delay);
+ }).catch(console.error);
+}
+
+DelayedTask.prototype = {
+ /**
+ * Return a promise that is resolved after the task is performed or canceled.
+ */
+ get promise() {
+ return this._promise;
+ },
+
+ /**
+ * Cancel the execution of the task.
+ */
+ cancel() {
+ clearTimeout(this.timeout);
+ if (this.runTask) {
+ this.runTask(true);
+ }
+ },
+
+ /**
+ * Execute the scheduled task immediately, without waiting for the timeout.
+ * Resolves the promise correctly.
+ */
+ runNow() {
+ clearTimeout(this.timeout);
+ if (this.runTask) {
+ this.runTask();
+ }
+ },
+};
+
+module.exports = batchingMiddleware;
diff --git a/devtools/client/netmonitor/src/middleware/event-telemetry.js b/devtools/client/netmonitor/src/middleware/event-telemetry.js
new file mode 100644
index 0000000000..026dfab4ba
--- /dev/null
+++ b/devtools/client/netmonitor/src/middleware/event-telemetry.js
@@ -0,0 +1,192 @@
+/* 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 {
+ TOGGLE_REQUEST_FILTER_TYPE,
+ ENABLE_REQUEST_FILTER_TYPE_ONLY,
+ SET_REQUEST_FILTER_TEXT,
+ SELECT_DETAILS_PANEL_TAB,
+ SEND_CUSTOM_REQUEST,
+ ENABLE_PERSISTENT_LOGS,
+ MSG_SELECT,
+} = require("resource://devtools/client/netmonitor/src/constants.js");
+
+const {
+ CHANGE_NETWORK_THROTTLING,
+} = require("resource://devtools/client/shared/components/throttling/actions.js");
+
+/**
+ * Event telemetry middleware is responsible for logging
+ * various events to telemetry. This helps to track Network
+ * panel usage.
+ */
+function eventTelemetryMiddleware(connector, telemetry) {
+ return store => next => action => {
+ const oldState = store.getState();
+ const res = next(action);
+ const toolbox = connector.getToolbox();
+ if (!toolbox) {
+ return res;
+ }
+
+ if (action.skipTelemetry) {
+ return res;
+ }
+
+ const state = store.getState();
+
+ const filterChangeActions = [
+ TOGGLE_REQUEST_FILTER_TYPE,
+ ENABLE_REQUEST_FILTER_TYPE_ONLY,
+ SET_REQUEST_FILTER_TEXT,
+ ];
+
+ // Record telemetry event when filter changes.
+ if (filterChangeActions.includes(action.type)) {
+ filterChange({
+ action,
+ state,
+ oldState,
+ telemetry,
+ });
+ }
+
+ // Record telemetry event when side panel is selected.
+ if (action.type == SELECT_DETAILS_PANEL_TAB) {
+ sidePanelChange({
+ state,
+ oldState,
+ telemetry,
+ });
+ }
+
+ // Record telemetry event when a request is resent.
+ if (action.type == SEND_CUSTOM_REQUEST) {
+ sendCustomRequest({
+ telemetry,
+ });
+ }
+
+ // Record telemetry event when throttling is changed.
+ if (action.type == CHANGE_NETWORK_THROTTLING) {
+ throttlingChange({
+ action,
+ telemetry,
+ });
+ }
+
+ // Record telemetry event when log persistence changes.
+ if (action.type == ENABLE_PERSISTENT_LOGS) {
+ persistenceChange({
+ telemetry,
+ state,
+ });
+ }
+
+ // Record telemetry event when message is selected.
+ if (action.type == MSG_SELECT) {
+ selectMessage({
+ telemetry,
+ });
+ }
+
+ return res;
+ };
+}
+
+/**
+ * This helper function is executed when filter related action is fired.
+ * It's responsible for recording "filters_changed" telemetry event.
+ */
+function filterChange({ action, state, oldState, telemetry }) {
+ const oldFilterState = oldState.filters;
+ const filterState = state.filters;
+ const activeFilters = [];
+ const inactiveFilters = [];
+
+ for (const [key, value] of Object.entries(filterState.requestFilterTypes)) {
+ if (value) {
+ activeFilters.push(key);
+ } else {
+ inactiveFilters.push(key);
+ }
+ }
+
+ let trigger;
+ if (
+ action.type === TOGGLE_REQUEST_FILTER_TYPE ||
+ action.type === ENABLE_REQUEST_FILTER_TYPE_ONLY
+ ) {
+ trigger = action.filter;
+ } else if (action.type === SET_REQUEST_FILTER_TEXT) {
+ if (
+ oldFilterState.requestFilterText !== "" &&
+ filterState.requestFilterText !== ""
+ ) {
+ return;
+ }
+
+ trigger = "text";
+ }
+
+ telemetry.recordEvent("filters_changed", "netmonitor", null, {
+ trigger,
+ active: activeFilters.join(","),
+ inactive: inactiveFilters.join(","),
+ });
+}
+
+/**
+ * This helper function is executed when side panel is selected.
+ * It's responsible for recording "sidepanel_tool_changed"
+ * telemetry event.
+ */
+function sidePanelChange({ state, oldState, telemetry }) {
+ telemetry.recordEvent("sidepanel_changed", "netmonitor", null, {
+ oldpanel: oldState.ui.detailsPanelSelectedTab,
+ newpanel: state.ui.detailsPanelSelectedTab,
+ });
+}
+
+/**
+ * This helper function is executed when a request is resent.
+ * It's responsible for recording "edit_resend" telemetry event.
+ */
+function sendCustomRequest({ telemetry }) {
+ telemetry.recordEvent("edit_resend", "netmonitor");
+}
+
+/**
+ * This helper function is executed when network throttling is changed.
+ * It's responsible for recording "throttle_changed" telemetry event.
+ */
+function throttlingChange({ action, telemetry }) {
+ telemetry.recordEvent("throttle_changed", "netmonitor", null, {
+ mode: action.profile,
+ });
+}
+
+/**
+ * This helper function is executed when log persistence is changed.
+ * It's responsible for recording "persist_changed" telemetry event.
+ */
+function persistenceChange({ telemetry, state }) {
+ telemetry.recordEvent(
+ "persist_changed",
+ "netmonitor",
+ String(state.ui.persistentLogsEnabled)
+ );
+}
+
+/**
+ * This helper function is executed when a WS frame is selected.
+ * It's responsible for recording "select_ws_frame" telemetry event.
+ */
+function selectMessage({ telemetry }) {
+ telemetry.recordEvent("select_ws_frame", "netmonitor");
+}
+
+module.exports = eventTelemetryMiddleware;
diff --git a/devtools/client/netmonitor/src/middleware/moz.build b/devtools/client/netmonitor/src/middleware/moz.build
new file mode 100644
index 0000000000..4e551c24c8
--- /dev/null
+++ b/devtools/client/netmonitor/src/middleware/moz.build
@@ -0,0 +1,11 @@
+# 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",
+ "event-telemetry.js",
+ "prefs.js",
+ "request-blocking.js",
+ "throttling.js",
+)
diff --git a/devtools/client/netmonitor/src/middleware/prefs.js b/devtools/client/netmonitor/src/middleware/prefs.js
new file mode 100644
index 0000000000..6034a95dbf
--- /dev/null
+++ b/devtools/client/netmonitor/src/middleware/prefs.js
@@ -0,0 +1,116 @@
+/* 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,
+ RESET_COLUMNS,
+ TOGGLE_COLUMN,
+ TOGGLE_REQUEST_FILTER_TYPE,
+ ENABLE_PERSISTENT_LOGS,
+ DISABLE_BROWSER_CACHE,
+ SET_COLUMNS_WIDTH,
+ WS_TOGGLE_COLUMN,
+ WS_RESET_COLUMNS,
+} = require("resource://devtools/client/netmonitor/src/constants.js");
+
+/**
+ * Update the relevant prefs when:
+ * - a column has been toggled
+ * - a filter type has been set
+ */
+function prefsMiddleware(store) {
+ return next => action => {
+ const res = next(action);
+ switch (action.type) {
+ case ENABLE_REQUEST_FILTER_TYPE_ONLY:
+ case TOGGLE_REQUEST_FILTER_TYPE:
+ const filters = Object.entries(
+ store.getState().filters.requestFilterTypes
+ )
+ .filter(([type, check]) => check)
+ .map(([type, check]) => type);
+ Services.prefs.setCharPref(
+ "devtools.netmonitor.filters",
+ JSON.stringify(filters)
+ );
+ break;
+ case ENABLE_PERSISTENT_LOGS:
+ const enabled = store.getState().ui.persistentLogsEnabled;
+ Services.prefs.setBoolPref("devtools.netmonitor.persistlog", enabled);
+ break;
+ case DISABLE_BROWSER_CACHE:
+ Services.prefs.setBoolPref(
+ "devtools.cache.disabled",
+ store.getState().ui.browserCacheDisabled
+ );
+ break;
+ case TOGGLE_COLUMN:
+ persistVisibleColumns(store.getState());
+ break;
+ case RESET_COLUMNS:
+ persistVisibleColumns(store.getState());
+ persistColumnsData(store.getState());
+ break;
+ case SET_COLUMNS_WIDTH:
+ persistColumnsData(store.getState());
+ break;
+ case WS_TOGGLE_COLUMN:
+ case WS_RESET_COLUMNS:
+ persistVisibleWebSocketsColumns(store.getState());
+ break;
+ }
+ return res;
+ };
+}
+
+/**
+ * Store list of visible columns into preferences.
+ */
+function persistVisibleColumns(state) {
+ const visibleColumns = [];
+ const { columns } = state.ui;
+ for (const column in columns) {
+ if (columns[column]) {
+ visibleColumns.push(column);
+ }
+ }
+
+ Services.prefs.setCharPref(
+ "devtools.netmonitor.visibleColumns",
+ JSON.stringify(visibleColumns)
+ );
+}
+
+/**
+ * Store list of visible columns into preferences.
+ */
+function persistVisibleWebSocketsColumns(state) {
+ const visibleColumns = [];
+ const { columns } = state.messages;
+ for (const column in columns) {
+ if (columns[column]) {
+ visibleColumns.push(column);
+ }
+ }
+
+ Services.prefs.setCharPref(
+ "devtools.netmonitor.msg.visibleColumns",
+ JSON.stringify(visibleColumns)
+ );
+}
+
+/**
+ * Store columns data (width, min-width, etc.) into preferences.
+ */
+function persistColumnsData(state) {
+ const columnsData = [...state.ui.columnsData.values()];
+ Services.prefs.setCharPref(
+ "devtools.netmonitor.columnsData",
+ JSON.stringify(columnsData)
+ );
+}
+
+module.exports = prefsMiddleware;
diff --git a/devtools/client/netmonitor/src/middleware/request-blocking.js b/devtools/client/netmonitor/src/middleware/request-blocking.js
new file mode 100644
index 0000000000..bb2a310a8d
--- /dev/null
+++ b/devtools/client/netmonitor/src/middleware/request-blocking.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 {
+ ADD_BLOCKED_URL,
+ REMOVE_BLOCKED_URL,
+ TOGGLE_BLOCKED_URL,
+ UPDATE_BLOCKED_URL,
+ TOGGLE_BLOCKING_ENABLED,
+ DISABLE_MATCHING_URLS,
+ ENABLE_ALL_BLOCKED_URLS,
+ DISABLE_ALL_BLOCKED_URLS,
+ REMOVE_ALL_BLOCKED_URLS,
+ REQUEST_BLOCKING_UPDATE_COMPLETE,
+} = require("resource://devtools/client/netmonitor/src/constants.js");
+
+const BLOCKING_EVENTS = [
+ ADD_BLOCKED_URL,
+ REMOVE_BLOCKED_URL,
+ TOGGLE_BLOCKED_URL,
+ UPDATE_BLOCKED_URL,
+ TOGGLE_BLOCKING_ENABLED,
+ DISABLE_MATCHING_URLS,
+ ENABLE_ALL_BLOCKED_URLS,
+ DISABLE_ALL_BLOCKED_URLS,
+ REMOVE_ALL_BLOCKED_URLS,
+];
+
+/**
+ * This middleware is responsible for syncing the list of blocking patterns/urls with the backed.
+ * It utilizes the NetworkCommand and `setBlockedUrls` function to sent the current list to the server
+ * every time it's been modified.
+ */
+function requestBlockingMiddleware(commands) {
+ return store => next => async action => {
+ const res = next(action);
+
+ if (BLOCKING_EVENTS.includes(action.type)) {
+ const { blockedUrls, blockingEnabled } = store.getState().requestBlocking;
+ const urls = blockingEnabled
+ ? blockedUrls.reduce((arr, { enabled, url }) => {
+ if (enabled) {
+ arr.push(url);
+ }
+ return arr;
+ }, [])
+ : [];
+ await commands.networkCommand.setBlockedUrls(urls);
+ store.dispatch({ type: REQUEST_BLOCKING_UPDATE_COMPLETE });
+ }
+ return res;
+ };
+}
+
+module.exports = requestBlockingMiddleware;
diff --git a/devtools/client/netmonitor/src/middleware/throttling.js b/devtools/client/netmonitor/src/middleware/throttling.js
new file mode 100644
index 0000000000..30f5a9b5f4
--- /dev/null
+++ b/devtools/client/netmonitor/src/middleware/throttling.js
@@ -0,0 +1,26 @@
+/* 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 {
+ CHANGE_NETWORK_THROTTLING,
+} = require("resource://devtools/client/shared/components/throttling/actions.js");
+
+/**
+ * Network throttling middleware is responsible for
+ * updating/syncing currently connected backend
+ * according to user actions.
+ */
+function throttlingMiddleware(connector) {
+ return store => next => action => {
+ const res = next(action);
+ if (action.type === CHANGE_NETWORK_THROTTLING) {
+ connector.updateNetworkThrottling(action.enabled, action.profile);
+ }
+ return res;
+ };
+}
+
+module.exports = throttlingMiddleware;