From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../client/netmonitor/src/middleware/batching.js | 146 ++++++++++++++++ .../netmonitor/src/middleware/event-telemetry.js | 192 +++++++++++++++++++++ .../client/netmonitor/src/middleware/moz.build | 11 ++ devtools/client/netmonitor/src/middleware/prefs.js | 116 +++++++++++++ .../netmonitor/src/middleware/request-blocking.js | 58 +++++++ .../client/netmonitor/src/middleware/throttling.js | 26 +++ 6 files changed, 549 insertions(+) create mode 100644 devtools/client/netmonitor/src/middleware/batching.js create mode 100644 devtools/client/netmonitor/src/middleware/event-telemetry.js create mode 100644 devtools/client/netmonitor/src/middleware/moz.build create mode 100644 devtools/client/netmonitor/src/middleware/prefs.js create mode 100644 devtools/client/netmonitor/src/middleware/request-blocking.js create mode 100644 devtools/client/netmonitor/src/middleware/throttling.js (limited to 'devtools/client/netmonitor/src/middleware') 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; -- cgit v1.2.3