diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/client/netmonitor/src/selectors/index.js | 13 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/selectors/messages.js | 162 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/selectors/moz.build | 12 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/selectors/requests.js | 198 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/selectors/search.js | 33 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/selectors/timing-markers.js | 18 | ||||
-rw-r--r-- | devtools/client/netmonitor/src/selectors/ui.js | 81 |
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, +}; |