summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/src/selectors
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /devtools/client/netmonitor/src/selectors
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/netmonitor/src/selectors')
-rw-r--r--devtools/client/netmonitor/src/selectors/index.js13
-rw-r--r--devtools/client/netmonitor/src/selectors/messages.js162
-rw-r--r--devtools/client/netmonitor/src/selectors/moz.build12
-rw-r--r--devtools/client/netmonitor/src/selectors/requests.js198
-rw-r--r--devtools/client/netmonitor/src/selectors/search.js33
-rw-r--r--devtools/client/netmonitor/src/selectors/timing-markers.js18
-rw-r--r--devtools/client/netmonitor/src/selectors/ui.js81
7 files changed, 517 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/selectors/index.js b/devtools/client/netmonitor/src/selectors/index.js
new file mode 100644
index 0000000000..52243d9a64
--- /dev/null
+++ b/devtools/client/netmonitor/src/selectors/index.js
@@ -0,0 +1,13 @@
+/* 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 requests = require("resource://devtools/client/netmonitor/src/selectors/requests.js");
+const search = require("resource://devtools/client/netmonitor/src/selectors/search.js");
+const timingMarkers = require("resource://devtools/client/netmonitor/src/selectors/timing-markers.js");
+const ui = require("resource://devtools/client/netmonitor/src/selectors/ui.js");
+const messages = require("resource://devtools/client/netmonitor/src/selectors/messages.js");
+
+Object.assign(exports, search, requests, timingMarkers, ui, messages);
diff --git a/devtools/client/netmonitor/src/selectors/messages.js b/devtools/client/netmonitor/src/selectors/messages.js
new file mode 100644
index 0000000000..d12b465b94
--- /dev/null
+++ b/devtools/client/netmonitor/src/selectors/messages.js
@@ -0,0 +1,162 @@
+/* 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 {
+ createSelector,
+} = require("resource://devtools/client/shared/vendor/reselect.js");
+
+/**
+ * Returns list of messages that are visible to the user.
+ * Filtered messages by types and text are factored in.
+ */
+const getDisplayedMessages = createSelector(
+ state => state.messages,
+ ({
+ messages,
+ messageFilterType,
+ showControlFrames,
+ messageFilterText,
+ currentChannelId,
+ }) => {
+ if (!currentChannelId || !messages.get(currentChannelId)) {
+ return [];
+ }
+
+ const messagesArray = messages.get(currentChannelId);
+ if (messageFilterType === "all" && messageFilterText.length === 0) {
+ return messagesArray.filter(message =>
+ typeFilter(message, messageFilterType, showControlFrames)
+ );
+ }
+
+ const filter = searchFilter(messageFilterText);
+
+ // If message payload is > 10,000 characters long, we check the LongStringActor payload string
+ return messagesArray.filter(
+ message =>
+ (message.payload.initial
+ ? filter(message.payload.initial)
+ : filter(message.payload)) &&
+ typeFilter(message, messageFilterType, showControlFrames)
+ );
+ }
+);
+
+function typeFilter(message, messageFilterType, showControlFrames) {
+ const controlFrames = [0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf];
+ const isControlFrame = controlFrames.includes(message.opCode);
+ if (messageFilterType === "all" || messageFilterType === message.type) {
+ return showControlFrames || !isControlFrame;
+ }
+ return false;
+}
+
+function searchFilter(messageFilterText) {
+ let regex;
+ if (looksLikeRegex(messageFilterText)) {
+ try {
+ regex = regexFromText(messageFilterText);
+ } catch (e) {}
+ }
+
+ return regex
+ ? payload => regex.test(payload)
+ : payload => payload.includes(messageFilterText);
+}
+
+function looksLikeRegex(text) {
+ return text.startsWith("/") && text.endsWith("/") && text.length > 2;
+}
+
+function regexFromText(text) {
+ return new RegExp(text.slice(1, -1), "im");
+}
+
+/**
+ * Checks if the selected message is visible.
+ * If the selected message is not visible, the SplitBox component
+ * should not show the MessagePayload component.
+ */
+const isSelectedMessageVisible = createSelector(
+ state => state.messages,
+ getDisplayedMessages,
+ ({ selectedMessage }, displayedMessages) =>
+ displayedMessages.some(message => message === selectedMessage)
+);
+
+/**
+ * Returns the current selected message.
+ */
+const getSelectedMessage = createSelector(
+ state => state.messages,
+ ({ selectedMessage }) => (selectedMessage ? selectedMessage : undefined)
+);
+
+/**
+ * Returns summary data of the list of messages that are visible to the user.
+ * Filtered messages by types and text are factored in.
+ */
+const getDisplayedMessagesSummary = createSelector(
+ getDisplayedMessages,
+ displayedMessages => {
+ let firstStartedMs = +Infinity;
+ let lastEndedMs = -Infinity;
+ let sentSize = 0;
+ let receivedSize = 0;
+ let totalSize = 0;
+
+ displayedMessages.forEach(message => {
+ if (message.type == "received") {
+ receivedSize += message.payload.length;
+ } else if (message.type == "sent") {
+ sentSize += message.payload.length;
+ }
+ totalSize += message.payload.length;
+ if (message.timeStamp < firstStartedMs) {
+ firstStartedMs = message.timeStamp;
+ }
+ if (message.timeStamp > lastEndedMs) {
+ lastEndedMs = message.timeStamp;
+ }
+ });
+
+ return {
+ count: displayedMessages.length,
+ totalMs: (lastEndedMs - firstStartedMs) / 1000,
+ sentSize,
+ receivedSize,
+ totalSize,
+ };
+ }
+);
+
+/**
+ * Returns if the currentChannelId is closed
+ */
+const isCurrentChannelClosed = createSelector(
+ state => state.messages,
+ ({ closedConnections, currentChannelId }) =>
+ closedConnections.has(currentChannelId)
+);
+
+/**
+ * Returns the closed connection details of the currentChannelId
+ * Null, if the connection is still open
+ */
+const getClosedConnectionDetails = createSelector(
+ state => state.messages,
+ ({ closedConnections, currentChannelId }) =>
+ closedConnections.get(currentChannelId)
+);
+
+module.exports = {
+ getSelectedMessage,
+ isSelectedMessageVisible,
+ getDisplayedMessages,
+ getDisplayedMessagesSummary,
+ isCurrentChannelClosed,
+ getClosedConnectionDetails,
+};
diff --git a/devtools/client/netmonitor/src/selectors/moz.build b/devtools/client/netmonitor/src/selectors/moz.build
new file mode 100644
index 0000000000..dcc4eac916
--- /dev/null
+++ b/devtools/client/netmonitor/src/selectors/moz.build
@@ -0,0 +1,12 @@
+# 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(
+ "index.js",
+ "messages.js",
+ "requests.js",
+ "search.js",
+ "timing-markers.js",
+ "ui.js",
+)
diff --git a/devtools/client/netmonitor/src/selectors/requests.js b/devtools/client/netmonitor/src/selectors/requests.js
new file mode 100644
index 0000000000..afe9b7ecaf
--- /dev/null
+++ b/devtools/client/netmonitor/src/selectors/requests.js
@@ -0,0 +1,198 @@
+/* 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 {
+ createSelector,
+} = require("resource://devtools/client/shared/vendor/reselect.js");
+const {
+ Filters,
+ isFreetextMatch,
+} = require("resource://devtools/client/netmonitor/src/utils/filter-predicates.js");
+const {
+ Sorters,
+} = require("resource://devtools/client/netmonitor/src/utils/sort-predicates.js");
+
+/**
+ * Take clones into account when sorting.
+ * If a request is a clone, use the original request for comparison.
+ * If one of the compared request is a clone of the other, sort them next to each other.
+ */
+function sortWithClones(requests, sorter, a, b) {
+ const aId = a.id,
+ bId = b.id;
+
+ if (aId.endsWith("-clone")) {
+ const aOrigId = aId.replace(/-clone$/, "");
+ if (aOrigId === bId) {
+ return +1;
+ }
+ a = requests.find(item => item.id === aOrigId);
+ }
+
+ if (bId.endsWith("-clone")) {
+ const bOrigId = bId.replace(/-clone$/, "");
+ if (bOrigId === aId) {
+ return -1;
+ }
+ b = requests.find(item => item.id === bOrigId);
+ }
+
+ const defaultSorter = () => false;
+ return sorter ? sorter(a, b) : defaultSorter;
+}
+
+/**
+ * Take clones into account when filtering. If a request is
+ * a clone, it's not filtered out.
+ */
+const getFilterWithCloneFn = createSelector(
+ state => state.filters,
+ filters => r => {
+ const matchesType = Object.keys(filters.requestFilterTypes).some(filter => {
+ if (r.id.endsWith("-clone")) {
+ return true;
+ }
+ return (
+ filters.requestFilterTypes[filter] &&
+ Filters[filter] &&
+ Filters[filter](r)
+ );
+ });
+ return matchesType && isFreetextMatch(r, filters.requestFilterText);
+ }
+);
+
+const getTypeFilterFn = createSelector(
+ state => state.filters,
+ filters => r => {
+ return Object.keys(filters.requestFilterTypes).some(filter => {
+ return (
+ filters.requestFilterTypes[filter] &&
+ Filters[filter] &&
+ Filters[filter](r)
+ );
+ });
+ }
+);
+
+const getSortFn = createSelector(
+ state => state.requests,
+ state => state.sort,
+ ({ requests }, sort) => {
+ const sorter = Sorters[sort.type || "waterfall"];
+ const ascending = sort.ascending ? +1 : -1;
+ return (a, b) => ascending * sortWithClones(requests, sorter, a, b);
+ }
+);
+
+const getSortedRequests = createSelector(
+ state => state.requests,
+ getSortFn,
+ ({ requests }, sortFn) => [...requests].sort(sortFn)
+);
+
+const getDisplayedRequests = createSelector(
+ state => state.requests,
+ getFilterWithCloneFn,
+ getSortFn,
+ ({ requests }, filterFn, sortFn) => requests.filter(filterFn).sort(sortFn)
+);
+
+const getTypeFilteredRequests = createSelector(
+ state => state.requests,
+ getTypeFilterFn,
+ ({ requests }, filterFn) => requests.filter(filterFn)
+);
+
+const getDisplayedRequestsSummary = createSelector(
+ getDisplayedRequests,
+ state => state.requests.lastEndedMs - state.requests.firstStartedMs,
+ (requests, totalMs) => {
+ if (requests.length === 0) {
+ return { count: 0, bytes: 0, ms: 0 };
+ }
+
+ const totalBytes = requests.reduce(
+ (totals, item) => {
+ if (typeof item.contentSize == "number") {
+ totals.contentSize += item.contentSize;
+ }
+
+ if (
+ typeof item.transferredSize == "number" &&
+ !(item.fromCache || item.status === "304")
+ ) {
+ totals.transferredSize += item.transferredSize;
+ }
+
+ return totals;
+ },
+ { contentSize: 0, transferredSize: 0 }
+ );
+
+ return {
+ count: requests.length,
+ contentSize: totalBytes.contentSize,
+ ms: totalMs,
+ transferredSize: totalBytes.transferredSize,
+ };
+ }
+);
+
+const getSelectedRequest = createSelector(
+ state => state.requests,
+ ({ selectedId, requests }) =>
+ selectedId ? requests.find(item => item.id === selectedId) : undefined
+);
+
+const isSelectedRequestVisible = createSelector(
+ state => state.requests,
+ getDisplayedRequests,
+ ({ selectedId }, displayedRequests) =>
+ displayedRequests.some(r => r.id === selectedId)
+);
+
+function getRequestById(state, id) {
+ return state.requests.requests.find(item => item.id === id);
+}
+
+function getRequestByChannelId(state, channelId) {
+ return [...state.requests.requests.values()].find(
+ r => r.resourceId == channelId
+ );
+}
+
+function getDisplayedRequestById(state, id) {
+ return getDisplayedRequests(state).find(r => r.id === id);
+}
+
+/**
+ * Returns the current recording boolean state (HTTP traffic is
+ * monitored or not monitored)
+ */
+function getRecordingState(state) {
+ return state.requests.recording;
+}
+
+const getClickedRequest = createSelector(
+ state => state.requests,
+ ({ requests, clickedRequestId }) =>
+ requests.find(request => request.id == clickedRequestId)
+);
+
+module.exports = {
+ getClickedRequest,
+ getDisplayedRequestById,
+ getDisplayedRequests,
+ getDisplayedRequestsSummary,
+ getRecordingState,
+ getRequestById,
+ getRequestByChannelId,
+ getSelectedRequest,
+ getSortedRequests,
+ getTypeFilteredRequests,
+ isSelectedRequestVisible,
+};
diff --git a/devtools/client/netmonitor/src/selectors/search.js b/devtools/client/netmonitor/src/selectors/search.js
new file mode 100644
index 0000000000..8ab0191a42
--- /dev/null
+++ b/devtools/client/netmonitor/src/selectors/search.js
@@ -0,0 +1,33 @@
+/* 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";
+
+function getOngoingSearch(state) {
+ return state.search.ongoingSearch;
+}
+
+function getSearchStatus(state) {
+ return state.search.status;
+}
+
+function getSearchResultCount(state) {
+ const { results } = state.search;
+ return (
+ (results.length !== 0
+ ? results.reduce((total, current) => total + current.results.length, 0)
+ : 0) + ""
+ );
+}
+
+function getSearchResourceCount(state) {
+ return state.search.results.length + "";
+}
+
+module.exports = {
+ getOngoingSearch,
+ getSearchStatus,
+ getSearchResultCount,
+ getSearchResourceCount,
+};
diff --git a/devtools/client/netmonitor/src/selectors/timing-markers.js b/devtools/client/netmonitor/src/selectors/timing-markers.js
new file mode 100644
index 0000000000..3855529904
--- /dev/null
+++ b/devtools/client/netmonitor/src/selectors/timing-markers.js
@@ -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/. */
+
+"use strict";
+
+function getDisplayedTimingMarker(state, marker) {
+ const value = state.timingMarkers[marker];
+ if (value == -1) {
+ return value;
+ }
+
+ return value - state.timingMarkers.firstDocumentRequestStartTimestamp;
+}
+
+module.exports = {
+ getDisplayedTimingMarker,
+};
diff --git a/devtools/client/netmonitor/src/selectors/ui.js b/devtools/client/netmonitor/src/selectors/ui.js
new file mode 100644
index 0000000000..d9d8995a71
--- /dev/null
+++ b/devtools/client/netmonitor/src/selectors/ui.js
@@ -0,0 +1,81 @@
+/* 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 {
+ createSelector,
+} = require("resource://devtools/client/shared/vendor/reselect.js");
+const {
+ REQUESTS_WATERFALL,
+} = require("resource://devtools/client/netmonitor/src/constants.js");
+
+const EPSILON = 0.001;
+
+const getWaterfallScale = createSelector(
+ state => state.requests.firstStartedMs,
+ state => state.requests.lastEndedMs,
+ state => state.timingMarkers.firstDocumentDOMContentLoadedTimestamp,
+ state => state.timingMarkers.firstDocumentLoadTimestamp,
+ state => state.ui.waterfallWidth,
+ (
+ firstStartedMs,
+ lastEndedMs,
+ firstDocumentDOMContentLoadedTimestamp,
+ firstDocumentLoadTimestamp,
+ waterfallWidth
+ ) => {
+ if (firstStartedMs === +Infinity || waterfallWidth === null) {
+ return null;
+ }
+
+ const lastEventMs = Math.max(
+ lastEndedMs,
+ firstDocumentDOMContentLoadedTimestamp,
+ firstDocumentLoadTimestamp
+ );
+ const longestWidth = lastEventMs - firstStartedMs;
+
+ // Reduce 20px for the last request's requests-list-timings-total
+ return Math.min(
+ Math.max(
+ (waterfallWidth - REQUESTS_WATERFALL.LABEL_WIDTH - 20) / longestWidth,
+ EPSILON
+ ),
+ 1
+ );
+ }
+);
+
+function getVisibleColumns(columns) {
+ return Object.entries(columns).filter(([_, shown]) => shown);
+}
+
+const getColumns = createSelector(
+ state => state.ui,
+ state => state.search,
+ (ui, search) => {
+ if (
+ ((ui.networkDetailsOpen || search.panelOpen) &&
+ getVisibleColumns(ui.columns).length === 1 &&
+ ui.columns.waterfall) ||
+ (!ui.networkDetailsOpen && !search.panelOpen)
+ ) {
+ return ui.columns;
+ }
+
+ // Remove the Waterfall/Timeline column from the list of available
+ // columns if the details side-bar is opened and more than one column is
+ // visible.
+ const columns = { ...ui.columns };
+ delete columns.waterfall;
+ return columns;
+ }
+);
+
+module.exports = {
+ getColumns,
+ getVisibleColumns,
+ getWaterfallScale,
+};