From fe94991ea2bf5703f6da9a444b7bfc1f7b3e8068 Mon Sep 17 00:00:00 2001 From: adel-signal Date: Tue, 23 Dec 2025 10:46:37 -0800 Subject: [PATCH] Remove calling dev tools --- .gitignore | 1 - ACKNOWLEDGMENTS.md | 30 - _locales/en/messages.json | 8 - app/main.main.ts | 62 -- app/menu.std.ts | 5 - calling_tools.html | 75 --- js/calling-tools/assert.js | 19 - js/calling-tools/candidate_grid.js | 219 ------- js/calling-tools/data_series.js | 133 ---- js/calling-tools/dump_creator.js | 170 ----- .../peer_connection_update_table.js | 263 -------- js/calling-tools/stats_graph_helper.js | 307 --------- js/calling-tools/stats_helper.js | 57 -- js/calling-tools/stats_rates_calculator.js | 609 ------------------ js/calling-tools/stats_table.js | 219 ------- js/calling-tools/tab_view.js | 115 ---- js/calling-tools/timeline_graph_view.js | 548 ---------------- js/calling-tools/user_media_table.js | 178 ----- js/calling-tools/util.js | 88 --- js/calling-tools/webrtc_internals.dom.js | 509 --------------- package.json | 1 - scripts/esbuild.js | 14 - scripts/generate-acknowledgments.js | 37 -- ts/services/calling.preload.ts | 56 -- ts/test-node/app/menu_test.node.ts | 3 - ts/types/menu.std.ts | 1 - ts/util/lint/license_comments.node.ts | 16 - ts/windows/calling-tools/preload.preload.ts | 25 - .../calling-tools/webrtc_internals.dom.ts | 4 - ts/windows/main/start.preload.ts | 2 - 30 files changed, 3774 deletions(-) delete mode 100644 calling_tools.html delete mode 100644 js/calling-tools/assert.js delete mode 100644 js/calling-tools/candidate_grid.js delete mode 100644 js/calling-tools/data_series.js delete mode 100644 js/calling-tools/dump_creator.js delete mode 100644 js/calling-tools/peer_connection_update_table.js delete mode 100644 js/calling-tools/stats_graph_helper.js delete mode 100644 js/calling-tools/stats_helper.js delete mode 100644 js/calling-tools/stats_rates_calculator.js delete mode 100644 js/calling-tools/stats_table.js delete mode 100644 js/calling-tools/tab_view.js delete mode 100644 js/calling-tools/timeline_graph_view.js delete mode 100644 js/calling-tools/user_media_table.js delete mode 100644 js/calling-tools/util.js delete mode 100644 js/calling-tools/webrtc_internals.dom.js delete mode 100644 ts/windows/calling-tools/preload.preload.ts delete mode 100644 ts/windows/calling-tools/webrtc_internals.dom.ts diff --git a/.gitignore b/.gitignore index e8645b1149..8c91a6f0cc 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,6 @@ js/util_worker.js libtextsecure/components.js stylesheets/*.css !stylesheets/tailwind-config.css -!stylesheets/webrtc_internals.css /storybook-static/ preload.bundle.* preload.wrapper.js diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index 7f177773d9..996a315fd4 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -6084,36 +6084,6 @@ Signal Desktop makes use of the following open source projects. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -## Chromium WebRTC Internals Dashboard - - Copyright 2015 The Chromium Authors - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer - in the documentation and/or other materials provided with the - distribution. - * Neither the name of Google LLC nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ## Kyber Patent License diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 93ac88ae65..17754cdfad 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1007,14 +1007,6 @@ "messageformat": "Sharing screen", "description": "Title for screen sharing window" }, - "icu:callingDeveloperTools": { - "messageformat": "Calling Developer Tools", - "description": "Title for calling developer tools window" - }, - "icu:callingDeveloperToolsDescription": { - "messageformat": "This window is used during development to display diagnostics from ongoing calls.", - "description": "Description displayed for calling developer tools window" - }, "icu:speech": { "messageformat": "Speech", "description": "Item under the Edit menu, with 'start/stop speaking' items below it" diff --git a/app/main.main.ts b/app/main.main.ts index e11213476c..d3ccf9f504 100644 --- a/app/main.main.ts +++ b/app/main.main.ts @@ -1326,67 +1326,6 @@ async function showScreenShareWindow(sourceName: string | undefined) { ); } -let callingDevToolsWindow: BrowserWindow | undefined; -async function showCallingDevToolsWindow() { - if (callingDevToolsWindow) { - callingDevToolsWindow.show(); - return; - } - - const options = { - height: 1200, - width: 1000, - alwaysOnTop: false, - autoHideMenuBar: true, - backgroundColor: '#ffffff', - darkTheme: false, - frame: true, - fullscreenable: true, - maximizable: true, - minimizable: true, - resizable: true, - show: false, - title: getResolvedMessagesLocale().i18n('icu:callingDeveloperTools'), - titleBarStyle: nonMainTitleBarStyle, - webPreferences: { - ...defaultWebPrefs, - nodeIntegration: false, - nodeIntegrationInWorker: false, - sandbox: true, - contextIsolation: true, - nativeWindowOpen: true, - preload: join(__dirname, '../bundles/calling-tools/preload.preload.js'), - }, - }; - - callingDevToolsWindow = new BrowserWindow(options); - - await handleCommonWindowEvents(callingDevToolsWindow); - - callingDevToolsWindow.once('closed', () => { - callingDevToolsWindow = undefined; - - mainWindow?.webContents.send('calling:set-rtc-stats-interval', null); - }); - - ipc.on('calling:set-rtc-stats-interval', (_, intervalMillis: number) => { - mainWindow?.webContents.send( - 'calling:set-rtc-stats-interval', - intervalMillis - ); - }); - - ipc.on('calling:rtc-stats-report', (_, report) => { - callingDevToolsWindow?.webContents.send('calling:rtc-stats-report', report); - }); - - await safeLoadURL( - callingDevToolsWindow, - await prepareFileUrl([__dirname, '../calling_tools.html']) - ); - callingDevToolsWindow.show(); -} - let aboutWindow: BrowserWindow | undefined; async function showAbout() { if (aboutWindow) { @@ -2396,7 +2335,6 @@ function setupMenu(options?: Partial) { stageLocalBackupForImport, showAbout, showDebugLog: showDebugLogWindow, - showCallingDevTools: showCallingDevToolsWindow, showKeyboardShortcuts, showSettings: () => { if (!settingsChannel) { diff --git a/app/menu.std.ts b/app/menu.std.ts index dcd65c0b68..838b797bbc 100644 --- a/app/menu.std.ts +++ b/app/menu.std.ts @@ -38,7 +38,6 @@ export const createTemplate = ( forceUpdate, showAbout, showDebugLog, - showCallingDevTools, showKeyboardShortcuts, showSettings, openArtCreator, @@ -150,10 +149,6 @@ export const createTemplate = ( role: 'toggleDevTools' as const, label: i18n('icu:viewMenuToggleDevTools'), }, - { - label: i18n('icu:viewMenuOpenCallingDevTools'), - click: showCallingDevTools, - }, ] : []), ...(devTools && platform !== 'linux' diff --git a/calling_tools.html b/calling_tools.html deleted file mode 100644 index 6feca4469d..0000000000 --- a/calling_tools.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - -

-

-

- - - - - - - - - - diff --git a/js/calling-tools/assert.js b/js/calling-tools/assert.js deleted file mode 100644 index 8a74c7187b..0000000000 --- a/js/calling-tools/assert.js +++ /dev/null @@ -1,19 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details -export function assert(value, message) { - if (value) { - return; - } - throw new Error("Assertion failed" + (message ? `: ${message}` : "")); - } - export function assertInstanceof(value, type, message) { - if (value instanceof type) { - return; - } - throw new Error( - message || `Value ${value} is not of type ${type.name || typeof type}`, - ); - } - export function assertNotReached(message = "Unreachable code hit") { - assert(false, message); - } - \ No newline at end of file diff --git a/js/calling-tools/candidate_grid.js b/js/calling-tools/candidate_grid.js deleted file mode 100644 index a5586d4acb..0000000000 --- a/js/calling-tools/candidate_grid.js +++ /dev/null @@ -1,219 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -/** - * Creates a ICE candidate grid. - * @param {Element} peerConnectionElement - */ - -import {$} from './util.js'; -/** - * A helper function for appending a child element to |parent|. - * Copied from webrtc_internals.js - * - * @param {!Element} parent The parent element. - * @param {string} tag The child element tag. - * @param {string} text The textContent of the new DIV. - * @return {!Element} the new DIV element. - */ -function appendChildWithText(parent, tag, text) { - const child = document.createElement(tag); - child.textContent = text; - parent.appendChild(child); - return child; -} - -export function createIceCandidateGrid(peerConnectionElement) { - const container = document.createElement('details'); - appendChildWithText(container, 'summary', 'ICE candidate grid'); - - const table = document.createElement('table'); - table.id = 'grid-' + peerConnectionElement.id; - table.className = 'candidategrid'; - container.appendChild(table); - - const tableHeader = document.createElement('tr'); - table.append(tableHeader); - - // For candidate pairs. - appendChildWithText(tableHeader, 'th', 'Candidate (pair) id'); - // [1] is used for both candidate pairs and individual candidates. - appendChildWithText(tableHeader, 'th', 'State / Candidate type'); - // For individual candidates. - appendChildWithText(tableHeader, 'th', 'Network type / address'); - appendChildWithText(tableHeader, 'th', 'Port'); - appendChildWithText(tableHeader, 'th', 'Protocol / candidate type'); - appendChildWithText(tableHeader, 'th', '(Pair) Priority'); - - // For candidate pairs. - appendChildWithText(tableHeader, 'th', 'Bytes sent / received'); - appendChildWithText(tableHeader, 'th', - 'STUN requests sent / responses received'); - appendChildWithText(tableHeader, 'th', - 'STUN requests received / responses sent'); - appendChildWithText(tableHeader, 'th', 'RTT'); - appendChildWithText(tableHeader, 'th', 'Last data sent / received'); - appendChildWithText(tableHeader, 'th', 'Last update'); - - peerConnectionElement.appendChild(container); -} - -/** - * Creates or returns a table row in the ICE candidate grid. - * @param {string} peerConnectionElement id - * @param {string} stat object id - * @param {type} type of the row - */ -function findOrCreateGridRow(peerConnectionElementId, statId, type) { - const elementId = 'grid-' + peerConnectionElementId - + '-' + statId + '-' + type; - let row = document.getElementById(elementId); - if (!row) { - row = document.createElement('tr'); - row.id = elementId; - for (let i = 0; i < 12; i++) { - row.appendChild(document.createElement('td')); - } - $('grid-' + peerConnectionElementId).appendChild(row); - } - return row; -} - -/** - * Updates a table row in the ICE candidate grid. - * @param {string} peerConnectionElement id - * @param {boolean} whether the pair is the selected pair of a transport - * (displayed bold) - * @param {object} candidate pair stats report - * @param {Map} full map of stats - */ -function appendRow(peerConnectionElement, active, candidatePair, stats) { - const pairRow = findOrCreateGridRow(peerConnectionElement.id, - candidatePair.id, 'candidatepair'); - pairRow.classList.add('candidategrid-candidatepair') - if (active) { - pairRow.classList.add('candidategrid-active'); - } - // Set transport-specific fields. - pairRow.children[0].innerText = candidatePair.id; - pairRow.children[1].innerText = candidatePair.state; - // Show (pair) priority as hex. - pairRow.children[5].innerText = - '0x' + parseInt(candidatePair.priority, 10).toString(16); - pairRow.children[6].innerText = - candidatePair.bytesSent + ' / ' + candidatePair.bytesReceived; - pairRow.children[7].innerText = candidatePair.requestsSent + ' / ' + - candidatePair.responsesReceived; - pairRow.children[8].innerText = candidatePair.requestsReceived + ' / ' + - candidatePair.responsesSent; - pairRow.children[9].innerText = - candidatePair.currentRoundTripTime !== undefined ? - candidatePair.currentRoundTripTime + 's' : ''; - if (candidatePair.lastPacketSentTimestamp) { - pairRow.children[10].innerText = - (new Date(candidatePair.lastPacketSentTimestamp)) - .toLocaleTimeString() + ' / ' + - (new Date(candidatePair.lastPacketReceivedTimestamp)) - .toLocaleTimeString(); - } - pairRow.children[11].innerText = (new Date()).toLocaleTimeString(); - - // Local candidate. - const localRow = findOrCreateGridRow(peerConnectionElement.id, - candidatePair.id, 'local'); - localRow.className = 'candidategrid-candidate' - const localCandidate = stats.get(candidatePair.localCandidateId); - ['id', 'type', 'address', 'port', 'candidateType', - 'priority'].forEach((stat, index) => { - // `relayProtocol` is only set for local relay candidates. - if (stat == 'candidateType' && localCandidate.relayProtocol) { - localRow.children[index].innerText = localCandidate[stat] + - '(' + localCandidate.relayProtocol + ')'; - if (localCandidate.url) { - localRow.children[index].innerText += '\n' + localCandidate.url; - } - } else if (stat === 'priority') { - const priority = parseInt(localCandidate[stat], 10) & 0xFFFFFFFF; - localRow.children[index].innerText = '0x' + priority.toString(16) + - // RFC 5245 - 4.1.2.1. - // priority = (2^24)*(type preference) + - // (2^8)*(local preference) + - // (2^0)*(256 - component ID) - '\n' + (priority >> 24) + - ' | ' + ((priority >> 8) & 0xFFFF) + - ' | ' + (priority & 0xFF); - } else if (stat === 'address') { - localRow.children[index].innerText = localCandidate[stat] || '(not set)'; - } else { - localRow.children[index].innerText = localCandidate[stat]; - } - }); - // `networkType` is only known for the local candidate so put it into the - // pair row above the address. Also highlight VPN adapters. - pairRow.children[2].innerText = localCandidate.networkType; - if (localCandidate['vpn'] === true) { - pairRow.children[2].innerText += ' (VPN)'; - } - // `protocol` must always be the same for the pair - // so put it into the pair row above the candidate type. - // Add `tcpType` for local candidates. - pairRow.children[4].innerText = localCandidate.protocol; - if (localCandidate.tcpType) { - pairRow.children[4].innerText += ' ' + localCandidate.tcpType; - } - - // Remote candidate. - const remoteRow = findOrCreateGridRow(peerConnectionElement.id, - candidatePair.id, 'remote'); - remoteRow.className = 'candidategrid-candidate' - const remoteCandidate = stats.get(candidatePair.remoteCandidateId); - ['id', 'type', 'address', 'port', 'candidateType', - 'priority'].forEach((stat, index) => { - if (stat === 'priority') { - remoteRow.children[index].innerText = '0x' + - parseInt(remoteCandidate[stat], 10).toString(16); - } else if (stat === 'address') { - remoteRow.children[index].innerText = remoteCandidate[stat] || - '(not set)'; - } else { - remoteRow.children[index].innerText = remoteCandidate[stat]; - } - }); - return pairRow; -} - -/** - * Updates the (spec) ICE candidate grid. - * @param {Element} peerConnectionElement - * @param {Map} stats reconstructed stats object. - */ -export function updateIceCandidateGrid(peerConnectionElement, stats) { - const container = $('grid-' + peerConnectionElement.id); - // Remove the active/bold marker from all rows. - container.childNodes.forEach(row => { - row.classList.remove('candidategrid-active'); - }); - let activePairIds = []; - // Find the active transport(s), then find its candidate pair - // and display it first. Note that previously selected pairs continue to be - // shown since rows are not removed. - stats.forEach(transportReport => { - if (transportReport.type !== 'transport') { - return; - } - if (!transportReport.selectedCandidatePairId) { - return; - } - activePairIds.push(transportReport.selectedCandidatePairId); - appendRow(peerConnectionElement, true, - stats.get(transportReport.selectedCandidatePairId), stats); - }); - - // Then iterate over the other candidate pairs. - stats.forEach(report => { - if (report.type !== 'candidate-pair' || activePairIds.includes(report.id)) { - return; - } - appendRow(peerConnectionElement, false, report, stats); - }); -} - diff --git a/js/calling-tools/data_series.js b/js/calling-tools/data_series.js deleted file mode 100644 index 0fd5b01951..0000000000 --- a/js/calling-tools/data_series.js +++ /dev/null @@ -1,133 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -// The maximum number of data points buffered for each stats. Old data points -// will be shifted out when the buffer is full. -export const MAX_STATS_DATA_POINT_BUFFER_SIZE = 1000; - -/** - * A TimelineDataSeries collects an ordered series of (time, value) pairs, - * and converts them to graph points. It also keeps track of its color and - * current visibility state. - * It keeps MAX_STATS_DATA_POINT_BUFFER_SIZE data points at most. Old data - * points will be dropped when it reaches this size. - */ -export class TimelineDataSeries { - constructor(statsType) { - // List of DataPoints in chronological order. - this.dataPoints_ = []; - - // Default color. Should always be overridden prior to display. - this.color_ = 'red'; - // Whether or not the data series should be drawn. - this.isVisible_ = true; - - this.cacheStartTime_ = null; - this.cacheStepSize_ = 0; - this.cacheValues_ = []; - this.statsType_ = statsType; - } - - /** - * @override - */ - toJSON() { - if (this.dataPoints_.length < 1) { - return {}; - } - - const values = []; - for (let i = 0; i < this.dataPoints_.length; ++i) { - values.push(this.dataPoints_[i].value); - } - return { - startTime: this.dataPoints_[0].time, - endTime: this.dataPoints_[this.dataPoints_.length - 1].time, - statsType: this.statsType_, - values: JSON.stringify(values), - }; - } - - /** - * Adds a DataPoint to |this| with the specified time and value. - * DataPoints are assumed to be received in chronological order. - */ - addPoint(timeTicks, value) { - const time = new Date(timeTicks); - this.dataPoints_.push(new DataPoint(time, value)); - - if (this.dataPoints_.length > MAX_STATS_DATA_POINT_BUFFER_SIZE) { - this.dataPoints_.shift(); - } - } - - isVisible() { - return this.isVisible_; - } - - show(isVisible) { - this.isVisible_ = isVisible; - } - - getColor() { - return this.color_; - } - - setColor(color) { - this.color_ = color; - } - - getCount() { - return this.dataPoints_.length; - } - /** - * Returns a list containing the values of the data series at |count| - * points, starting at |startTime|, and |stepSize| milliseconds apart. - * Caches values, so showing/hiding individual data series is fast. - */ - getValues(startTime, stepSize, count) { - // Use cached values, if we can. - if (this.cacheStartTime_ === startTime && - this.cacheStepSize_ === stepSize && - this.cacheValues_.length === count) { - return this.cacheValues_; - } - - // Do all the work. - this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count); - this.cacheStartTime_ = startTime; - this.cacheStepSize_ = stepSize; - - return this.cacheValues_; - } - - /** - * Returns the cached |values| in the specified time period. - */ - getValuesInternal_(startTime, stepSize, count) { - const values = []; - let nextPoint = 0; - let currentValue = 0; - let time = startTime; - for (let i = 0; i < count; ++i) { - while (nextPoint < this.dataPoints_.length && - this.dataPoints_[nextPoint].time < time) { - currentValue = this.dataPoints_[nextPoint].value; - ++nextPoint; - } - values[i] = currentValue; - time += stepSize; - } - return values; - } -} - -/** - * A single point in a data series. Each point has a time, in the form of - * milliseconds since the Unix epoch, and a numeric value. - */ -class DataPoint { - constructor(time, value) { - this.time = time; - this.value = value; - } -} diff --git a/js/calling-tools/dump_creator.js b/js/calling-tools/dump_creator.js deleted file mode 100644 index 4314ca65da..0000000000 --- a/js/calling-tools/dump_creator.js +++ /dev/null @@ -1,170 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -import {$} from './util.js'; - -/** A list of getUserMedia requests. */ -export const userMediaRequests = []; -/** A map from peer connection id to the PeerConnectionRecord. */ -export const peerConnectionDataStore = {}; - -// Also duplicating on window since tests access these from C++. -window.userMediaRequests = userMediaRequests; -window.peerConnectionDataStore = peerConnectionDataStore; - -/** - * Provides the UI for dump creation. - */ -export class DumpCreator { - /** - * @param {Element} containerElement The parent element of the dump creation - * UI. - */ - constructor(containerElement) { - /** - * The root element of the dump creation UI. - * @type {Element} - * @private - */ - // Signal change, remove unused diagnostic UI - } - - createDumpRoot(containerElement) { - this.dumpRoot_ = document.createElement('details'); - - this.dumpRoot_.className = 'peer-connection-dump-root'; - containerElement.appendChild(this.dumpRoot_); - const summary = document.createElement('summary'); - this.dumpRoot_.appendChild(summary); - summary.textContent = 'Create a WebRTC-Internals dump'; - const content = document.createElement('div'); - this.dumpRoot_.appendChild(content); - content.appendChild($('dump-template').content.cloneNode(true)); - content.getElementsByTagName('a')[0].addEventListener( - 'click', this.onDownloadData_.bind(this)); - } - - createAudioRecordingRoot(containerElement) { - this.audioRoot_ = document.createElement('details'); - - this.audioRoot_.className = 'peer-connection-dump-root'; - containerElement.appendChild(this.audioRoot_); - const summary = document.createElement('summary'); - this.audioRoot_.appendChild(summary); - summary.textContent = 'Create diagnostic audio recordings'; - const content = document.createElement('div'); - this.audioRoot_.appendChild(content); - content.appendChild($('audio-recording-template').content.cloneNode(true)); - content.getElementsByTagName('input')[0].addEventListener( - 'click', this.onAudioDebugRecordingsChanged_.bind(this)); - - } - - createPacketRecordingRoot(containerElement) { - this.packetRoot_ = document.createElement('details'); - - this.packetRoot_.className = 'peer-connection-dump-root'; - containerElement.appendChild(this.packetRoot_); - const summary = document.createElement('summary'); - this.packetRoot_.appendChild(summary); - summary.textContent = 'Create diagnostic packet recordings'; - const content = document.createElement('div'); - this.packetRoot_.appendChild(content); - content.appendChild($('packet-recording-template').content.cloneNode(true)); - content.getElementsByTagName('input')[0].addEventListener( - 'click', this.onEventLogRecordingsChanged_.bind(this)); - } - - // Mark the diagnostic audio recording checkbox checked. - setAudioDebugRecordingsCheckbox() { - this.audioRoot_.getElementsByTagName('input')[0].checked = true; - } - - // Mark the diagnostic audio recording checkbox unchecked. - clearAudioDebugRecordingsCheckbox() { - this.audioRoot_.getElementsByTagName('input')[0].checked = false; - } - - // Mark the event log recording checkbox checked. - setEventLogRecordingsCheckbox() { - this.packetRoot_.getElementsByTagName('input')[0].checked = true; - } - - // Mark the event log recording checkbox unchecked. - clearEventLogRecordingsCheckbox() { - this.packetRoot_.getElementsByTagName('input')[0].checked = false; - } - - // Mark the event log recording checkbox as mutable/immutable. - setEventLogRecordingsCheckboxMutability(mutable) { - this.packetRoot_.getElementsByTagName('input')[0].disabled = !mutable; - if (!mutable) { - const label = this.packetRoot_.getElementsByTagName('label')[0]; - label.style = 'color:red;'; - label.textContent = - ' WebRTC event logging\'s state was set by a command line flag.'; - } - } - - /** - * Downloads the PeerConnection updates and stats data as a file. - * - * @private - */ - async onDownloadData_(event) { - const useCompression = this.dumpRoot_.getElementsByTagName('input')[0].checked; - const dumpObject = { - 'getUserMedia': userMediaRequests, - 'PeerConnections': peerConnectionDataStore, - 'UserAgent': navigator.userAgent, - }; - const textBlob = - new Blob([JSON.stringify(dumpObject, null, 1)], {type: 'octet/stream'}); - let url; - if (useCompression) { - const compressionStream = new CompressionStream('gzip'); - const binaryStream = textBlob.stream().pipeThrough(compressionStream); - const binaryBlob = await new Response(binaryStream).blob(); - url = URL.createObjectURL(binaryBlob); - // Since this is async we can't use the default event and need to click - // again (while avoiding an infinite loop). - const anchor = document.createElement('a'); - anchor.download = 'webrtc_internals_dump.gz' - anchor.href = url; - anchor.click(); - return; - } - url = URL.createObjectURL(textBlob); - const anchor = this.dumpRoot_.getElementsByTagName('a')[0]; - anchor.download = 'webrtc_internals_dump.txt' - anchor.href = url; - // The default action of the anchor will download the url. - } - - /** - * Handles the event of toggling the audio debug recordings state. - * - * @private - */ - onAudioDebugRecordingsChanged_() { - const enabled = this.audioRoot_.getElementsByTagName('input')[0].checked; - if (enabled) { - // chrome.send('enableAudioDebugRecordings'); - } else { - // chrome.send('disableAudioDebugRecordings'); - } - } - - /** - * Handles the event of toggling the event log recordings state. - * - * @private - */ - onEventLogRecordingsChanged_() { - const enabled = this.packetRoot_.getElementsByTagName('input')[0].checked; - if (enabled) { - // chrome.send('enableEventLogRecordings'); - } else { - // chrome.send('disableEventLogRecordings'); - } - } -} diff --git a/js/calling-tools/peer_connection_update_table.js b/js/calling-tools/peer_connection_update_table.js deleted file mode 100644 index 1793165949..0000000000 --- a/js/calling-tools/peer_connection_update_table.js +++ /dev/null @@ -1,263 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -import {$} from './util.js'; - -const MAX_NUMBER_OF_STATE_CHANGES_DISPLAYED = 10; -const MAX_NUMBER_OF_EXPANDED_MEDIASECTIONS = 10; -/** - * The data of a peer connection update. - * @param {number} pid The id of the renderer. - * @param {number} lid The id of the peer conneciton inside a renderer. - * @param {string} type The type of the update. - * @param {string} value The details of the update. - */ -class PeerConnectionUpdateEntry { - constructor(pid, lid, type, value) { - /** - * @type {number} - */ - this.pid = pid; - - /** - * @type {number} - */ - this.lid = lid; - - /** - * @type {string} - */ - this.type = type; - - /** - * @type {string} - */ - this.value = value; - } -} - -/** - * Maintains the peer connection update log table. - */ -export class PeerConnectionUpdateTable { - constructor() { - /** - * @type {string} - * @const - * @private - */ - this.UPDATE_LOG_ID_SUFFIX_ = '-update-log'; - - /** - * @type {string} - * @const - * @private - */ - this.UPDATE_LOG_CONTAINER_CLASS_ = 'update-log-container'; - - /** - * @type {string} - * @const - * @private - */ - this.UPDATE_LOG_TABLE_CLASS = 'update-log-table'; - } - - /** - * Adds the update to the update table as a new row. The type of the update - * is set to the summary of the cell; clicking the cell will reveal or hide - * the details as the content of a TextArea element. - * - * @param {!Element} peerConnectionElement The root element. - * @param {!PeerConnectionUpdateEntry} update The update to add. - */ - addPeerConnectionUpdate(peerConnectionElement, update) { - const tableElement = this.ensureUpdateContainer_(peerConnectionElement); - - const row = document.createElement('tr'); - tableElement.firstChild.appendChild(row); - - const time = new Date(parseFloat(update.time)); - const timeItem = document.createElement('td'); - timeItem.textContent = time.toLocaleString(); - row.appendChild(timeItem); - - // `type` is a display variant of update.type which does not get serialized - // into the JSON dump. - let type = update.type; - - if (update.value.length === 0) { - const typeItem = document.createElement('td'); - typeItem.textContent = type; - row.appendChild(typeItem); - return; - } - - if (update.type === 'icecandidate' || update.type === 'addIceCandidate') { - const parts = update.value.split(', '); - type += '(' + parts[0] + ', ' + parts[1]; // show sdpMid/sdpMLineIndex. - const candidateParts = parts[2].substr(11).split(' '); - if (candidateParts && candidateParts[7]) { // show candidate type. - type += ', type: ' + candidateParts[7]; - } - type += ')'; - } else if ( - update.type === 'createOfferOnSuccess' || - update.type === 'createAnswerOnSuccess') { - this.setLastOfferAnswer_(tableElement, update); - } else if (update.type === 'setLocalDescription') { - const lastOfferAnswer = this.getLastOfferAnswer_(tableElement); - if (update.value.startsWith('type: rollback')) { - this.setLastOfferAnswer_(tableElement, {value: undefined}) - } else if (lastOfferAnswer && update.value !== lastOfferAnswer) { - type += ' (munged)'; - } - } else if (update.type === 'setConfiguration') { - // Update the configuration that is displayed at the top. - peerConnectionElement.firstChild.children[2].textContent = update.value; - } else if (['transceiverAdded', - 'transceiverModified'].includes(update.type)) { - // Show the transceiver index. - const indexLine = update.value.split('\n', 3)[2]; - if (indexLine.startsWith('getTransceivers()[')) { - type += ' ' + indexLine.substring(17, indexLine.length - 2); - } - const kindLine = update.value.split('\n', 5)[4].trim(); - if (kindLine.startsWith('kind:')) { - type += ', ' + kindLine.substring(6, kindLine.length - 2); - } - } else if (['iceconnectionstatechange', 'connectionstatechange', - 'signalingstatechange'].includes(update.type)) { - const fieldName = { - 'iceconnectionstatechange' : 'iceconnectionstate', - 'connectionstatechange' : 'connectionstate', - 'signalingstatechange' : 'signalingstate', - }[update.type]; - const el = peerConnectionElement.getElementsByClassName(fieldName)[0]; - const numberOfEvents = el.textContent.split(' => ').length; - if (numberOfEvents < MAX_NUMBER_OF_STATE_CHANGES_DISPLAYED) { - el.textContent += ' => ' + update.value; - } else if (numberOfEvents >= MAX_NUMBER_OF_STATE_CHANGES_DISPLAYED) { - el.textContent += ' => ...'; - } - } - - const summaryItem = $('summary-template').content.cloneNode(true); - const summary = summaryItem.querySelector('summary'); - summary.textContent = type; - row.appendChild(summaryItem); - - const valueContainer = document.createElement('pre'); - const details = row.cells[1].childNodes[0]; - details.appendChild(valueContainer); - - // Highlight ICE/DTLS failures and failure callbacks. - if ((update.type === 'iceconnectionstatechange' && - update.value === 'failed') || - (update.type === 'connectionstatechange' && - update.value === 'failed') || - update.type.indexOf('OnFailure') !== -1 || - update.type === 'addIceCandidateFailed') { - valueContainer.parentElement.classList.add('update-log-failure'); - } - - // RTCSessionDescription is serialized as 'type: , sdp:' - if (update.value.indexOf(', sdp:') !== -1) { - const [type, sdp] = update.value.substr(6).split(', sdp: '); - if (type === 'rollback') { - // Rollback has no SDP. - summary.textContent += ' (type: "rollback")'; - } else { - // Create a copy-to-clipboard button. - const copyBtn = document.createElement('button'); - copyBtn.textContent = 'Copy description to clipboard'; - copyBtn.onclick = () => { - navigator.clipboard.writeText(JSON.stringify({type, sdp})); - }; - valueContainer.appendChild(copyBtn); - - // Fold the SDP sections. - const sections = sdp.split('\nm=') - .map((part, index) => (index > 0 ? - 'm=' + part : part).trim() + '\r\n'); - summary.textContent += - ' (type: "' + type + '", ' + sections.length + ' sections)'; - sections.forEach(section => { - const lines = section.trim().split('\n'); - // Extract the mid attribute. - const mid = lines - .filter(line => line.startsWith('a=mid:')) - .map(line => line.substr(6))[0]; - const sectionDetails = document.createElement('details'); - // Fold by default for large SDP. - sectionDetails.open = - sections.length <= MAX_NUMBER_OF_EXPANDED_MEDIASECTIONS; - sectionDetails.textContent = lines.slice(1).join('\n'); - - const sectionSummary = document.createElement('summary'); - sectionSummary.textContent = - lines[0].trim() + - ' (' + (lines.length - 1) + ' more lines)' + - (mid ? ' mid=' + mid : ''); - sectionDetails.appendChild(sectionSummary); - - valueContainer.appendChild(sectionDetails); - }); - } - } else { - valueContainer.textContent = update.value; - } - } - - /** - * Makes sure the update log table of the peer connection is created. - * - * @param {!Element} peerConnectionElement The root element. - * @return {!Element} The log table element. - * @private - */ - ensureUpdateContainer_(peerConnectionElement) { - const tableId = peerConnectionElement.id + this.UPDATE_LOG_ID_SUFFIX_; - - // Disable getElementById restriction here, since |tableId| is not always - // a valid selector. - // eslint-disable-next-line no-restricted-properties - let tableElement = document.getElementById(tableId); - if (!tableElement) { - const tableContainer = document.createElement('div'); - tableContainer.className = this.UPDATE_LOG_CONTAINER_CLASS_; - peerConnectionElement.appendChild(tableContainer); - - tableElement = document.createElement('table'); - tableElement.className = this.UPDATE_LOG_TABLE_CLASS; - tableElement.id = tableId; - tableElement.border = 1; - tableContainer.appendChild(tableElement); - tableElement.appendChild( - $('time-event-template').content.cloneNode(true)); - } - return tableElement; - } - - /** - * Store the last createOfferOnSuccess/createAnswerOnSuccess to compare to - * setLocalDescription and visualize SDP munging. - * - * @param {!Element} tableElement The peerconnection update element. - * @param {!PeerConnectionUpdateEntry} update The update to add. - * @private - */ - setLastOfferAnswer_(tableElement, update) { - tableElement['data-lastofferanswer'] = update.value; - } - - /** - * Retrieves the last createOfferOnSuccess/createAnswerOnSuccess to compare - * to setLocalDescription and visualize SDP munging. - * - * @param {!Element} tableElement The peerconnection update element. - * @private - */ - getLastOfferAnswer_(tableElement) { - return tableElement['data-lastofferanswer']; - } -} diff --git a/js/calling-tools/stats_graph_helper.js b/js/calling-tools/stats_graph_helper.js deleted file mode 100644 index e9e4ad02af..0000000000 --- a/js/calling-tools/stats_graph_helper.js +++ /dev/null @@ -1,307 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -// -// This file contains helper methods to draw the stats timeline graphs. -// Each graph represents a series of stats report for a PeerConnection, -// e.g. 1234-0-ssrc-abcd123-bytesSent is the graph for the series of bytesSent -// for ssrc-abcd123 of PeerConnection 0 in process 1234. -// The graphs are drawn as CANVAS, grouped per report type per PeerConnection. -// Each group has an expand/collapse button and is collapsed initially. -// - -import {$} from './util.js'; - -import {TimelineDataSeries} from './data_series.js'; -import {peerConnectionDataStore} from './dump_creator.js'; -import {generateStatsLabel} from './stats_helper.js'; -import {TimelineGraphView} from './timeline_graph_view.js'; - -const STATS_GRAPH_CONTAINER_HEADING_CLASS = 'stats-graph-container-heading'; - -function isReportBlocklisted(report) { - // Codec stats reflect what has been negotiated. They don't contain - // information that is useful in graphs. - if (report.type === 'codec') { - return true; - } - // Unused data channels can stay in "connecting" indefinitely and their - // counters stay zero. - if (report.type === 'data-channel' && - readReportStat(report, 'state') === 'connecting') { - return true; - } - // The same is true for transports and "new". - if (report.type === 'transport' && - readReportStat(report, 'dtlsState') === 'new') { - return true; - } - // Local and remote candidates don't change over time and there are several of - // them. - if (report.type === 'local-candidate' || report.type === 'remote-candidate') { - return true; - } - return false; -} - -function readReportStat(report, stat) { - const values = report.stats.values; - for (let i = 0; i < values.length; i += 2) { - if (values[i] === stat) { - return values[i + 1]; - } - } - return undefined; -} - -function isStatBlocklisted(report, statName) { - // The priority does not change over time on its own; plotting uninteresting. - if (report.type === 'candidate-pair' && statName === 'priority') { - return true; - } - // The mid/rid and ssrcs associated with a sender/receiver do not change - // over time; plotting uninteresting. - if (['inbound-rtp', 'outbound-rtp'].includes(report.type) && - ['mid', 'rid', 'ssrc', 'rtxSsrc', 'fecSsrc'].includes(statName)) { - return true; - } - return false; -} - -const graphViews = {}; -// Export on |window| since tests access this directly from C++. -window.graphViews = graphViews; -const graphElementsByPeerConnectionId = new Map(); - -// Returns number parsed from |value|, or NaN. -function getNumberFromValue(name, value) { - if (isNaN(value)) { - return NaN; - } - return parseFloat(value); -} - -// Adds the stats report |report| to the timeline graph for the given -// |peerConnectionElement|. -export function drawSingleReport( - peerConnectionElement, report) { - const reportType = report.type; - const reportId = report.id; - const stats = report.stats; - if (!stats || !stats.values) { - return; - } - - const childrenBefore = peerConnectionElement.hasChildNodes() ? - Array.from(peerConnectionElement.childNodes) : - []; - - for (let i = 0; i < stats.values.length - 1; i = i + 2) { - const rawLabel = stats.values[i]; - const rawDataSeriesId = reportId + '-' + rawLabel; - const rawValue = getNumberFromValue(rawLabel, stats.values[i + 1]); - if (isNaN(rawValue)) { - // We do not draw non-numerical values, but still want to record it in the - // data series. - addDataSeriesPoints( - peerConnectionElement, reportType, rawDataSeriesId, rawLabel, - [stats.timestamp], [stats.values[i + 1]]); - continue; - } - let finalDataSeriesId = rawDataSeriesId; - let finalLabel = rawLabel; - let finalValue = rawValue; - - // Updates the final dataSeries to draw. - addDataSeriesPoints( - peerConnectionElement, reportType, finalDataSeriesId, finalLabel, - [stats.timestamp], [finalValue]); - - if (isReportBlocklisted(report) || isStatBlocklisted(report, rawLabel)) { - // We do not want to draw certain reports but still want to - // record them in the data series. - continue; - } - - // Updates the graph. - const graphType = finalLabel; - const graphViewId = - peerConnectionElement.id + '-' + reportId + '-' + graphType; - - if (!graphViews[graphViewId]) { - graphViews[graphViewId] = - createStatsGraphView(peerConnectionElement, report, graphType); - const searchParameters = new URLSearchParams(window.location.search); - if (searchParameters.has('statsInterval')) { - const statsInterval = Math.max( - parseInt(searchParameters.get('statsInterval'), 10), - 100); - if (isFinite(statsInterval)) { - graphViews[graphViewId].setScale(statsInterval); - } - } - const date = new Date(stats.timestamp); - graphViews[graphViewId].setDateRange(date, date); - } - // Ensures the stats graph title is up-to-date. - ensureStatsGraphContainer(peerConnectionElement, report); - // Adds the new dataSeries to the graphView. We have to do it here to cover - // both the simple and compound graph cases. - const dataSeries = - peerConnectionDataStore[peerConnectionElement.id].getDataSeries( - finalDataSeriesId); - if (!graphViews[graphViewId].hasDataSeries(dataSeries)) { - graphViews[graphViewId].addDataSeries(dataSeries); - } - graphViews[graphViewId].updateEndDate(); - } - // Add a synthetic data series for the timestamp. - addDataSeriesPoints( - peerConnectionElement, reportType, reportId + '-timestamp', - reportId + '-timestamp', [stats.timestamp], [stats.timestamp]); - - const childrenAfter = peerConnectionElement.hasChildNodes() ? - Array.from(peerConnectionElement.childNodes) : - []; - for (let i = 0; i < childrenAfter.length; ++i) { - if (!childrenBefore.includes(childrenAfter[i])) { - let graphElements = - graphElementsByPeerConnectionId.get(peerConnectionElement.id); - if (!graphElements) { - graphElements = []; - graphElementsByPeerConnectionId.set( - peerConnectionElement.id, graphElements); - } - graphElements.push(childrenAfter[i]); - } - } -} - -export function removeStatsReportGraphs(peerConnectionElement) { - const graphElements = - graphElementsByPeerConnectionId.get(peerConnectionElement.id); - if (graphElements) { - for (let i = 0; i < graphElements.length; ++i) { - peerConnectionElement.removeChild(graphElements[i]); - } - graphElementsByPeerConnectionId.delete(peerConnectionElement.id); - } - Object.keys(graphViews).forEach(key => { - if (key.startsWith(peerConnectionElement.id)) { - delete graphViews[key]; - } - }); -} - -// Makes sure the TimelineDataSeries with id |dataSeriesId| is created, -// and adds the new data points to it. |times| is the list of timestamps for -// each data point, and |values| is the list of the data point values. -function addDataSeriesPoints( - peerConnectionElement, reportType, dataSeriesId, label, times, values) { - let dataSeries = - peerConnectionDataStore[peerConnectionElement.id].getDataSeries( - dataSeriesId); - if (!dataSeries) { - dataSeries = new TimelineDataSeries(reportType); - peerConnectionDataStore[peerConnectionElement.id].setDataSeries( - dataSeriesId, dataSeries); - } - for (let i = 0; i < times.length; ++i) { - dataSeries.addPoint(times[i], values[i]); - } -} - -// Ensures a div container to the stats graph for a peerConnectionElement is -// created as a child of the |peerConnectionElement|. -function ensureStatsGraphTopContainer(peerConnectionElement) { - const containerId = peerConnectionElement.id + '-graph-container'; - let container = document.getElementById(containerId); - if (!container) { - container = document.createElement('div'); - container.id = containerId; - container.className = 'stats-graph-container'; - const label = document.createElement('label'); - label.innerText = 'Filter statistics graphs by type including '; - container.appendChild(label); - const input = document.createElement('input'); - input.placeholder = 'separate multiple values by `,`'; - input.size = 25; - input.oninput = (e) => filterStats(e, container); - container.appendChild(input); - - peerConnectionElement.appendChild(container); - } - return container; -} - -// Ensures a div container to the stats graph for a single set of data is -// created as a child of the |peerConnectionElement|'s graph container. -function ensureStatsGraphContainer(peerConnectionElement, report) { - const topContainer = ensureStatsGraphTopContainer(peerConnectionElement); - const containerId = peerConnectionElement.id + '-' + report.type + '-' + - report.id + '-graph-container'; - // Disable getElementById restriction here, since |containerId| is not always - // a valid selector. - // eslint-disable-next-line no-restricted-properties - let container = document.getElementById(containerId); - if (!container) { - container = document.createElement('details'); - container.id = containerId; - container.className = 'stats-graph-container'; - container.attributes['data-statsType'] = report.type; - - peerConnectionElement.appendChild(container); - container.appendChild($('summary-span-template').content.cloneNode(true)); - container.firstChild.firstChild.className = - STATS_GRAPH_CONTAINER_HEADING_CLASS; - topContainer.appendChild(container); - } - // Update the label all the time to account for new information. - container.firstChild.firstChild.textContent = 'Stats graphs for ' + - generateStatsLabel(report); - return container; -} - -// Creates the container elements holding a timeline graph -// and the TimelineGraphView object. -function createStatsGraphView(peerConnectionElement, report, statsName) { - const topContainer = - ensureStatsGraphContainer(peerConnectionElement, report); - - const graphViewId = - peerConnectionElement.id + '-' + report.id + '-' + statsName; - const divId = graphViewId + '-div'; - const canvasId = graphViewId + '-canvas'; - const container = document.createElement('div'); - container.className = 'stats-graph-sub-container'; - - topContainer.appendChild(container); - const canvasDiv = $('container-template').content.cloneNode(true); - canvasDiv.querySelectorAll('div')[0].textContent = statsName; - canvasDiv.querySelectorAll('div')[1].id = divId; - canvasDiv.querySelector('canvas').id = canvasId; - container.appendChild(canvasDiv); - return new TimelineGraphView(divId, canvasId); -} - -/** - * Apply a filter to the stats graphs - * @param event InputEvent from the filter input field. - * @param container stats table container element. - * @private - */ -function filterStats(event, container) { - const filter = event.target.value; - const filters = filter.split(','); - container.childNodes.forEach(node => { - if (node.nodeName !== 'DETAILS') { - return; - } - const statsType = node.attributes['data-statsType']; - if (!filter || filters.includes(statsType) || - filters.find(f => statsType.includes(f))) { - node.style.display = 'block'; - } else { - node.style.display = 'none'; - } - }); -} diff --git a/js/calling-tools/stats_helper.js b/js/calling-tools/stats_helper.js deleted file mode 100644 index 0c41d67851..0000000000 --- a/js/calling-tools/stats_helper.js +++ /dev/null @@ -1,57 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -/** - * @param {!Object} statsValues The object containing stats, an - * array [key1, val1, key2, val2, ...] so searching a certain - * key needs to ensure it does not collide with a value. - */ -function generateLabel(key, statsValues) { - let label = ''; - const statIndex = statsValues.findIndex((value, index) => { - return value === key && index % 2 === 0; - }); - if (statIndex !== -1) { - label += key + '=' + statsValues[statIndex + 1]; - } - return label; -} - -/** - * Formats the display text used for a stats type that is shown - * in the stats table or the stats graph. - * - * @param {!Object} report The object containing stats, which is the object - * containing timestamp and values, which is an array of strings, whose - * even index entry is the name of the stat, and the odd index entry is - * the value. - */ -export function generateStatsLabel(report) { - let label = report.type + ' ('; - let labels = []; - if (['outbound-rtp', 'remote-outbound-rtp', 'inbound-rtp', - 'remote-inbound-rtp'].includes(report.type) && report.stats.values) { - labels = ['kind', 'mid', 'rid', 'ssrc', 'rtxSsrc', 'fecSsrc', - 'scalabilityMode', - 'encoderImplementation', 'decoderImplementation', - 'powerEfficientEncoder', 'powerEfficientDecoder', - '[codec]']; - } else if (['local-candidate', 'remote-candidate'].includes(report.type)) { - labels = ['candidateType', 'tcpType', 'relayProtocol']; - } else if (report.type === 'codec') { - labels = ['mimeType', 'payloadType']; - } else if (['media-playout', 'media-source'].includes(report.type)) { - labels = ['kind']; - } else if (report.type === 'candidate-pair') { - labels = ['state']; - } else if (report.type === 'transport') { - labels = ['iceState', 'dtlsState']; - } - labels = labels - .map(stat => generateLabel(stat, report.stats.values)) - .filter(label => !!label); - if (labels.length) { - label += labels.join(', ') + ', '; - } - label += 'id=' + report.id + ')'; - return label; -} diff --git a/js/calling-tools/stats_rates_calculator.js b/js/calling-tools/stats_rates_calculator.js deleted file mode 100644 index 8549fab741..0000000000 --- a/js/calling-tools/stats_rates_calculator.js +++ /dev/null @@ -1,609 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -const CalculatorModifier = Object.freeze({ - kNone: Object.freeze({postfix: '', multiplier: 1}), - kMillisecondsFromSeconds: - Object.freeze({postfix: '_in_ms', multiplier: 1000}), - kBytesToBits: Object.freeze({bitrate: true, multiplier: 8}), -}); - -class Metric { - constructor(name, value) { - this.name = name; - this.value = value; - } - - toString() { - return '{"' + this.name + '":"' + this.value + '"}'; - } -} - -// Represents a companion dictionary to an RTCStats object of an RTCStatsReport. -// The CalculatedStats object contains additional metrics associated with the -// original RTCStats object. Typically, the RTCStats object contains -// accumulative counters, but in chrome://webrc-internals/ we also want graphs -// for the average rate over the last second, so we have CalculatedStats -// containing calculated Metrics. -class CalculatedStats { - constructor(id) { - this.id = id; - // A map Original Name -> Array of Metrics, where Original Name refers to - // the name of the metric in the original RTCStats object, and the Metrics - // are calculated metrics. For example, if the original RTCStats report - // contains framesReceived, and from that we've calculated - // [framesReceived/s] and [framesReceived-framesDecoded], then there will be - // a mapping from "framesReceived" to an array of two Metric objects, - // "[framesReceived/s]" and "[framesReceived-framesDecoded]". - this.calculatedMetricsByOriginalName = new Map(); - } - - addCalculatedMetric(originalName, metric) { - let calculatedMetrics = - this.calculatedMetricsByOriginalName.get(originalName); - if (!calculatedMetrics) { - calculatedMetrics = []; - this.calculatedMetricsByOriginalName.set(originalName, calculatedMetrics); - } - calculatedMetrics.push(metric); - } - - // Gets the calculated metrics associated with |originalName| in the order - // that they were added, or an empty list if there are no associated metrics. - getCalculatedMetrics(originalName) { - const calculatedMetrics = - this.calculatedMetricsByOriginalName.get(originalName); - if (!calculatedMetrics) { - return []; - } - return calculatedMetrics; - } - - toString() { - let str = '{id:"' + this.id + '"'; - for (const originalName of this.calculatedMetricsByOriginalName.keys()) { - const calculatedMetrics = - this.calculatedMetricsByOriginalName.get(originalName); - str += ',' + originalName + ':['; - for (let i = 0; i < calculatedMetrics.length; i++) { - str += calculatedMetrics[i].toString(); - if (i + 1 < calculatedMetrics.length) { - str += ','; - } - str += ']'; - } - } - str += '}'; - return str; - } -} - -// Contains the metrics of an RTCStatsReport, as well as calculated metrics -// associated with metrics from the original report. Convertible to and from the -// "internal reports" format used by webrtc_internals.js to pass stats from C++ -// to JavaScript. -export class StatsReport { - constructor() { - // Represents an RTCStatsReport. It is a Map RTCStats.id -> RTCStats. - // https://w3c.github.io/webrtc-pc/#dom-rtcstatsreport - this.statsById = new Map(); - // RTCStats.id -> CalculatedStats - this.calculatedStatsById = new Map(); - } - - // |internalReports| is an array, each element represents an RTCStats object, - // but the format is a little different from the spec. This is the format: - // { - // id: "string", - // type: "string", - // stats: { - // timestamp: , - // values: ["member1", value1, "member2", value2...] - // } - // } - static fromInternalsReportList(internalReports) { - const result = new StatsReport(); - internalReports.forEach(internalReport => { - if (!internalReport.stats || !internalReport.stats.values) { - return; // continue; - } - const stats = { - id: internalReport.id, - type: internalReport.type, - timestamp: internalReport.stats.timestamp / 1000.0 // ms -> s - }; - const values = internalReport.stats.values; - for (let i = 0; i < values.length; i += 2) { - // Metric "name: value". - stats[values[i]] = values[i + 1]; - } - result.statsById.set(stats.id, stats); - }); - return result; - } - - toInternalsReportList() { - const result = []; - for (const stats of this.statsById.values()) { - const internalReport = { - id: stats.id, - type: stats.type, - stats: { - timestamp: stats.timestamp * 1000.0, // s -> ms - values: [] - } - }; - Object.keys(stats).forEach(metricName => { - if (metricName === 'id' || metricName === 'type' || - metricName === 'timestamp') { - return; // continue; - } - internalReport.stats.values.push(metricName); - internalReport.stats.values.push(stats[metricName]); - const calculatedMetrics = - this.getCalculatedMetrics(stats.id, metricName); - calculatedMetrics.forEach(calculatedMetric => { - internalReport.stats.values.push(calculatedMetric.name); - // Treat calculated metrics that are undefined as 0 to ensure graphs - // can be created anyway. - internalReport.stats.values.push( - calculatedMetric.value ? calculatedMetric.value : 0); - }); - }); - result.push(internalReport); - } - return result; - } - - toString() { - let str = ''; - for (const stats of this.statsById.values()) { - if (str !== '') { - str += ','; - } - str += JSON.stringify(stats); - } - let str2 = ''; - for (const stats of this.calculatedStatsById.values()) { - if (str2 !== '') { - str2 += ','; - } - str2 += stats.toString(); - } - return '[original:' + str + '],calculated:[' + str2 + ']'; - } - - get(id) { - return this.statsById.get(id); - } - - getByType(type) { - const result = []; - for (const stats of this.statsById.values()) { - if (stats.type === type) { - result.push(stats); - } - } - return result; - } - - addCalculatedMetric(id, insertAtOriginalMetricName, name, value) { - let calculatedStats = this.calculatedStatsById.get(id); - if (!calculatedStats) { - calculatedStats = new CalculatedStats(id); - this.calculatedStatsById.set(id, calculatedStats); - } - calculatedStats.addCalculatedMetric( - insertAtOriginalMetricName, new Metric(name, value)); - } - - getCalculatedMetrics(id, originalMetricName) { - const calculatedStats = this.calculatedStatsById.get(id); - return calculatedStats ? - calculatedStats.getCalculatedMetrics(originalMetricName) : - []; - } -} - -// Shows a `DOMHighResTimeStamp` as a human readable date time. -// The metric must be a time value in milliseconds with Unix epoch as time -// origin. -class DateCalculator { - constructor(metric) { - this.metric = metric; - } - getCalculatedMetricName() { - return '[' + this.metric + ']'; - } - calculate(id, previousReport, currentReport) { - const timestamp = currentReport.get(id)[this.metric]; - const date = new Date(timestamp); - return date.toLocaleString(); - } -} - -// Calculates the rate "delta accumulative / delta samples" and returns it. If -// a rate cannot be calculated, such as the metric is missing in the current -// or previous report, undefined is returned. -class RateCalculator { - constructor( - accumulativeMetric, samplesMetric, modifier = CalculatorModifier.kNone) { - this.accumulativeMetric = accumulativeMetric; - this.samplesMetric = samplesMetric; - this.modifier = modifier; - } - - getCalculatedMetricName() { - const accumulativeMetric = this.modifier.bitrate ? - this.accumulativeMetric + '_in_bits' : - this.accumulativeMetric; - if (this.samplesMetric === 'timestamp') { - return '[' + accumulativeMetric + '/s]'; - } - return '[' + accumulativeMetric + '/' + this.samplesMetric + - this.modifier.postfix + ']'; - } - - calculate(id, previousReport, currentReport) { - return RateCalculator.calculateRate( - id, previousReport, currentReport, this.accumulativeMetric, - this.samplesMetric) * - this.modifier.multiplier; - } - - static calculateRate( - id, previousReport, currentReport, accumulativeMetric, samplesMetric) { - if (!previousReport || !currentReport) { - return undefined; - } - const previousStats = previousReport.get(id); - const currentStats = currentReport.get(id); - if (!previousStats || !currentStats) { - return undefined; - } - const deltaTime = currentStats.timestamp - previousStats.timestamp; - if (deltaTime <= 0) { - return undefined; - } - // Try to convert whatever the values are to numbers. This gets around the - // fact that some types that are not supported by base::Value (e.g. uint32, - // int64, uint64 and double) are passed as strings. - const previousValue = Number(previousStats[accumulativeMetric]); - const currentValue = Number(currentStats[accumulativeMetric]); - if (typeof previousValue !== 'number' || typeof currentValue !== 'number') { - return undefined; - } - const previousSamples = Number(previousStats[samplesMetric]); - const currentSamples = Number(currentStats[samplesMetric]); - if (typeof previousSamples !== 'number' || - typeof currentSamples !== 'number') { - return undefined; - } - const deltaValue = currentValue - previousValue; - const deltaSamples = currentSamples - previousSamples; - return deltaValue / deltaSamples; - } -} - -// Looks up codec and payload type from a codecId reference, constructing an -// informative string about which codec is used. -class CodecCalculator { - getCalculatedMetricName() { - return '[codec]'; - } - - calculate(id, previousReport, currentReport) { - const targetStats = currentReport.get(id); - const codecStats = currentReport.get(targetStats.codecId); - if (!codecStats) { - return undefined; - } - // If mimeType is 'video/VP8' then codec is 'VP8'. - const codec = - codecStats.mimeType.substr(codecStats.mimeType.indexOf('/') + 1); - - let fmtpLine = ''; - if (codecStats.sdpFmtpLine) { - fmtpLine = ', ' + codecStats.sdpFmtpLine; - } - return codec + ' (' + codecStats.payloadType + fmtpLine + ')'; - } -} - -// Calculates "RMS" audio level, which is the average audio level between the -// previous and current report, in the interval [0,1]. Calculated per: -// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-totalaudioenergy -class AudioLevelRmsCalculator { - getCalculatedMetricName() { - return '[Audio_Level_in_RMS]'; - } - - calculate(id, previousReport, currentReport) { - const averageAudioLevelSquared = RateCalculator.calculateRate( - id, previousReport, currentReport, 'totalAudioEnergy', - 'totalSamplesDuration'); - return Math.sqrt(averageAudioLevelSquared); - } -} - -// Calculates "metricA - SUM(otherMetrics)", only looking at the current report. -class DifferenceCalculator { - constructor(metricA, ...otherMetrics) { - this.metricA = metricA; - this.otherMetrics = otherMetrics; - } - - getCalculatedMetricName() { - return '[' + this.metricA + '-' + this.otherMetrics.join('-') + ']'; - } - - calculate(id, previousReport, currentReport) { - const currentStats = currentReport.get(id); - return parseInt(currentStats[this.metricA], 10) - - this.otherMetrics.map(metric => parseInt(currentStats[metric], 10)) - .reduce((a, b) => a + b, 0); - } -} - -// Calculates the standard deviation from a totalSquaredSum, totalSum, and -// totalCount. If the standard deviation cannot be calculated, such as the -// metric is missing in the current or previous report, undefined is returned. -class StandardDeviationCalculator { - constructor(totalSquaredSumMetric, totalSumMetric, totalCount, label) { - this.totalSquaredSumMetric = totalSquaredSumMetric; - this.totalSumMetric = totalSumMetric; - this.totalCount = totalCount; - this.label = label; - } - - getCalculatedMetricName() { - return '[' + this.label + 'StDev_in_ms]'; - } - - calculate(id, previousReport, currentReport) { - return StandardDeviationCalculator.calculateStandardDeviation( - id, previousReport, currentReport, this.totalSquaredSumMetric, - this.totalSumMetric, this.totalCount); - } - - static calculateStandardDeviation( - id, previousReport, currentReport, totalSquaredSumMetric, totalSumMetric, - totalCount) { - if (!previousReport || !currentReport) { - return undefined; - } - const previousStats = previousReport.get(id); - const currentStats = currentReport.get(id); - if (!previousStats || !currentStats) { - return undefined; - } - const deltaCount = - Number(currentStats[totalCount]) - Number(previousStats[totalCount]); - if (deltaCount <= 0) { - return undefined; - } - // Try to convert whatever the values are to numbers. This gets around the - // fact that some types that are not supported by base::Value (e.g. uint32, - // int64, uint64 and double) are passed as strings. - const previousSquaredSumValue = - Number(previousStats[totalSquaredSumMetric]); - const currentSquaredSumValue = Number(currentStats[totalSquaredSumMetric]); - if (typeof previousSquaredSumValue !== 'number' || - typeof currentSquaredSumValue !== 'number') { - return undefined; - } - const previousSumValue = Number(previousStats[totalSumMetric]); - const currentSumValue = Number(currentStats[totalSumMetric]); - if (typeof previousSumValue !== 'number' || - typeof currentSumValue !== 'number') { - return undefined; - } - - const deltaSquaredSum = currentSquaredSumValue - previousSquaredSumValue; - const deltaSum = currentSumValue - previousSumValue; - const variance = - (deltaSquaredSum - Math.pow(deltaSum, 2) / deltaCount) / deltaCount; - if (variance < 0) { - return undefined; - } - return 1000 * Math.sqrt(variance); - } -} - -// Keeps track of previous and current stats report and calculates all -// calculated metrics. -export class StatsRatesCalculator { - constructor() { - this.previousReport = null; - this.currentReport = null; - this.statsCalculators = [ - { - type: 'data-channel', - metricCalculators: { - messagesSent: new RateCalculator('messagesSent', 'timestamp'), - messagesReceived: new RateCalculator('messagesReceived', 'timestamp'), - bytesSent: new RateCalculator( - 'bytesSent', 'timestamp', CalculatorModifier.kBytesToBits), - bytesReceived: new RateCalculator( - 'bytesReceived', 'timestamp', CalculatorModifier.kBytesToBits), - }, - }, - { - type: 'media-source', - metricCalculators: { - totalAudioEnergy: new AudioLevelRmsCalculator(), - }, - }, - { - type: 'outbound-rtp', - metricCalculators: { - bytesSent: new RateCalculator( - 'bytesSent', 'timestamp', CalculatorModifier.kBytesToBits), - headerBytesSent: new RateCalculator( - 'headerBytesSent', 'timestamp', CalculatorModifier.kBytesToBits), - retransmittedBytesSent: new RateCalculator( - 'retransmittedBytesSent', 'timestamp', - CalculatorModifier.kBytesToBits), - packetsSent: new RateCalculator('packetsSent', 'timestamp'), - retransmittedPacketsSent: - new RateCalculator('retransmittedPacketsSent', 'timestamp'), - totalPacketSendDelay: new RateCalculator( - 'totalPacketSendDelay', 'packetsSent', - CalculatorModifier.kMillisecondsFromSeconds), - framesEncoded: new RateCalculator('framesEncoded', 'timestamp'), - framesSent: new RateCalculator('framesSent', 'timestamp'), - totalEncodedBytesTarget: new RateCalculator( - 'totalEncodedBytesTarget', 'timestamp', - CalculatorModifier.kBytesToBits), - totalEncodeTime: new RateCalculator( - 'totalEncodeTime', 'framesEncoded', - CalculatorModifier.kMillisecondsFromSeconds), - qpSum: new RateCalculator('qpSum', 'framesEncoded'), - codecId: new CodecCalculator(), - }, - }, - { - type: 'inbound-rtp', - metricCalculators: { - bytesReceived: new RateCalculator( - 'bytesReceived', 'timestamp', CalculatorModifier.kBytesToBits), - headerBytesReceived: new RateCalculator( - 'headerBytesReceived', 'timestamp', - CalculatorModifier.kBytesToBits), - retransmittedBytesReceived: new RateCalculator( - 'retransmittedBytesReceived', 'timestamp', - CalculatorModifier.kBytesToBits), - fecBytesReceived: new RateCalculator( - 'fecBytesReceived', 'timestamp', - CalculatorModifier.kBytesToBits), - packetsReceived: new RateCalculator('packetsReceived', 'timestamp'), - packetsDiscarded: new RateCalculator('packetsDiscarded', 'timestamp'), - retransmittedPacketsReceived: - new RateCalculator('retransmittedPacketsReceived', 'timestamp'), - fecPacketsReceived: - new RateCalculator('fecPacketsReceived', 'timestamp'), - fecPacketsDiscarded: - new RateCalculator('fecPacketsDiscarded', 'timestamp'), - framesReceived: [ - new RateCalculator('framesReceived', 'timestamp'), - new DifferenceCalculator('framesReceived', - 'framesDecoded', 'framesDropped'), - ], - framesDecoded: new RateCalculator('framesDecoded', 'timestamp'), - keyFramesDecoded: new RateCalculator('keyFramesDecoded', 'timestamp'), - totalDecodeTime: new RateCalculator( - 'totalDecodeTime', 'framesDecoded', - CalculatorModifier.kMillisecondsFromSeconds), - totalInterFrameDelay: new RateCalculator( - 'totalInterFrameDelay', 'framesDecoded', - CalculatorModifier.kMillisecondsFromSeconds), - totalSquaredInterFrameDelay: new StandardDeviationCalculator( - 'totalSquaredInterFrameDelay', 'totalInterFrameDelay', - 'framesDecoded', 'interFrameDelay'), - totalSamplesReceived: - new RateCalculator('totalSamplesReceived', 'timestamp'), - concealedSamples: [ - new RateCalculator('concealedSamples', 'timestamp'), - new RateCalculator('concealedSamples', 'totalSamplesReceived'), - ], - silentConcealedSamples: - new RateCalculator('silentConcealedSamples', 'timestamp'), - insertedSamplesForDeceleration: - new RateCalculator('insertedSamplesForDeceleration', 'timestamp'), - removedSamplesForAcceleration: - new RateCalculator('removedSamplesForAcceleration', 'timestamp'), - qpSum: new RateCalculator('qpSum', 'framesDecoded'), - codecId: new CodecCalculator(), - totalAudioEnergy: new AudioLevelRmsCalculator(), - jitterBufferDelay: new RateCalculator( - 'jitterBufferDelay', 'jitterBufferEmittedCount', - CalculatorModifier.kMillisecondsFromSeconds), - jitterBufferTargetDelay: new RateCalculator( - 'jitterBufferTargetDelay', 'jitterBufferEmittedCount', - CalculatorModifier.kMillisecondsFromSeconds), - jitterBufferMinimumDelay: new RateCalculator( - 'jitterBufferMinimumDelay', 'jitterBufferEmittedCount', - CalculatorModifier.kMillisecondsFromSeconds), - lastPacketReceivedTimestamp: new DateCalculator( - 'lastPacketReceivedTimestamp'), - estimatedPlayoutTimestamp: new DateCalculator( - 'estimatedPlayoutTimestamp'), - totalProcessingDelay: new RateCalculator( - 'totalProcessingDelay', 'framesDecoded', - CalculatorModifier.kMillisecondsFromSeconds), - totalAssemblyTime: new RateCalculator( - 'totalAssemblyTime', 'framesAssembledFromMultiplePackets', - CalculatorModifier.kMillisecondsFromSeconds), - }, - }, - { - type: 'remote-outbound-rtp', - metricCalculators: { - remoteTimestamp: new DateCalculator('remoteTimestamp'), - }, - }, - { - type: 'transport', - metricCalculators: { - bytesSent: new RateCalculator( - 'bytesSent', 'timestamp', CalculatorModifier.kBytesToBits), - bytesReceived: new RateCalculator( - 'bytesReceived', 'timestamp', CalculatorModifier.kBytesToBits), - packetsSent: new RateCalculator( - 'packetsSent', 'timestamp'), - packetsReceived: new RateCalculator( - 'packetsReceived', 'timestamp'), - }, - }, - { - type: 'candidate-pair', - metricCalculators: { - bytesSent: new RateCalculator( - 'bytesSent', 'timestamp', CalculatorModifier.kBytesToBits), - bytesReceived: new RateCalculator( - 'bytesReceived', 'timestamp', CalculatorModifier.kBytesToBits), - packetsSent: new RateCalculator( - 'packetsSent', 'timestamp'), - packetsReceived: new RateCalculator( - 'packetsReceived', 'timestamp'), - totalRoundTripTime: - new RateCalculator('totalRoundTripTime', 'responsesReceived'), - lastPacketReceivedTimestamp: new DateCalculator( - 'lastPacketReceivedTimestamp'), - lastPacketSentTimestamp: new DateCalculator( - 'lastPacketSentTimestamp'), - }, - }, - ]; - } - - addStatsReport(report) { - this.previousReport = this.currentReport; - this.currentReport = report; - this.updateCalculatedMetrics_(); - } - - // Updates all "calculated metrics", which are metrics derived from standard - // values, such as converting total counters (e.g. bytesSent) to rates (e.g. - // bytesSent/s). - updateCalculatedMetrics_() { - this.statsCalculators.forEach(statsCalculator => { - this.currentReport.getByType(statsCalculator.type).forEach(stats => { - Object.keys(statsCalculator.metricCalculators) - .forEach(originalMetric => { - let metricCalculators = - statsCalculator.metricCalculators[originalMetric]; - if (!Array.isArray(metricCalculators)) { - metricCalculators = [metricCalculators]; - } - metricCalculators.forEach(metricCalculator => { - this.currentReport.addCalculatedMetric( - stats.id, originalMetric, - metricCalculator.getCalculatedMetricName(), - metricCalculator.calculate( - stats.id, this.previousReport, this.currentReport)); - }); - }); - }); - }); - } -} diff --git a/js/calling-tools/stats_table.js b/js/calling-tools/stats_table.js deleted file mode 100644 index bcf98c1773..0000000000 --- a/js/calling-tools/stats_table.js +++ /dev/null @@ -1,219 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -import {$} from './util.js'; - -import {generateStatsLabel} from './stats_helper.js'; - -/** - * Maintains the stats table. - */ -export class StatsTable { - constructor() {} - - /** - * Adds |report| to the stats table of |peerConnectionElement|. - * - * @param {!Element} peerConnectionElement The root element. - * @param {!Object} report The object containing stats, which is the object - * containing timestamp and values, which is an array of strings, whose - * even index entry is the name of the stat, and the odd index entry is - * the value. - */ - addStatsReport(peerConnectionElement, report) { - const statsTable = this.ensureStatsTable_(peerConnectionElement, report); - - // Update the label since information may have changed. - statsTable.parentElement.firstElementChild.innerText = - generateStatsLabel(report); - - if (report.stats) { - this.addStatsToTable_( - statsTable, report.stats.timestamp, report.stats.values); - } - } - - clearStatsLists(peerConnectionElement) { - const containerId = peerConnectionElement.id + '-table-container'; - // Disable getElementById restriction here, since |containerId| is not - // always a valid selector. - // eslint-disable-next-line no-restricted-properties - const container = document.getElementById(containerId); - if (container) { - peerConnectionElement.removeChild(container); - this.ensureStatsTableContainer_(peerConnectionElement); - } - } - - /** - * Ensure the DIV container for the stats tables is created as a child of - * |peerConnectionElement|. - * - * @param {!Element} peerConnectionElement The root element. - * @return {!Element} The stats table container. - * @private - */ - ensureStatsTableContainer_(peerConnectionElement) { - const containerId = peerConnectionElement.id + '-table-container'; - // Disable getElementById restriction here, since |containerId| is not - // always a valid selector. - // eslint-disable-next-line no-restricted-properties - let container = document.getElementById(containerId); - if (!container) { - container = document.createElement('div'); - container.id = containerId; - container.className = 'stats-table-container'; - const head = document.createElement('div'); - head.textContent = 'Stats Tables'; - container.appendChild(head); - const label = document.createElement('label'); - label.innerText = 'Filter statistics by type including '; - container.appendChild(label); - const input = document.createElement('input'); - input.placeholder = 'separate multiple values by `,`'; - input.size = 25; - input.oninput = (e) => this.filterStats(e, container); - container.appendChild(input); - peerConnectionElement.appendChild(container); - } - return container; - } - - /** - * Ensure the stats table for track specified by |report| of PeerConnection - * |peerConnectionElement| is created. - * - * @param {!Element} peerConnectionElement The root element. - * @param {!Object} report The object containing stats, which is the object - * containing timestamp and values, which is an array of strings, whose - * even index entry is the name of the stat, and the odd index entry is - * the value. - * @return {!Element} The stats table element. - * @private - */ - ensureStatsTable_(peerConnectionElement, report) { - const tableId = peerConnectionElement.id + '-table-' + report.id; - // Disable getElementById restriction here, since |tableId| is not - // always a valid selector. - // eslint-disable-next-line no-restricted-properties - let table = document.getElementById(tableId); - if (!table) { - const container = this.ensureStatsTableContainer_(peerConnectionElement); - const details = document.createElement('details'); - details.attributes['data-statsType'] = report.type; - container.appendChild(details); - - const summary = document.createElement('summary'); - summary.textContent = generateStatsLabel(report); - details.appendChild(summary); - - table = document.createElement('table'); - details.appendChild(table); - table.id = tableId; - table.border = 1; - - table.appendChild($('trth-template').content.cloneNode(true)); - table.rows[0].cells[0].textContent = 'Statistics ' + report.id; - } - return table; - } - - /** - * Update |statsTable| with |time| and |statsData|. - * - * @param {!Element} statsTable Which table to update. - * @param {number} time The number of milliseconds since epoch. - * @param {Array} statsData An array of stats name and value pairs. - * @private - */ - addStatsToTable_(statsTable, time, statsData) { - const definedMetrics = new Set(); - for (let i = 0; i < statsData.length - 1; i = i + 2) { - definedMetrics.add(statsData[i]); - } - // For any previously reported metric that is no longer defined, replace its - // now obsolete value with the magic string "(removed)". - const metricsContainer = statsTable.firstChild; - for (let i = 0; i < metricsContainer.children.length; ++i) { - const metricElement = metricsContainer.children[i]; - // `metricElement` IDs have the format `bla-bla-bla-bla-${metricName}`. - let metricName = - metricElement.id.substring(metricElement.id.lastIndexOf('-') + 1); - if (metricName.endsWith(']')) { - // Computed metrics may contain the '-' character (e.g. - // `DifferenceCalculator` based metrics) in which case `metricName` will - // not have been parsed correctly. Instead look for starting '['. - metricName = - metricElement.id.substring(metricElement.id.indexOf('[')); - } - if (metricName && metricName != 'timestamp' && - !definedMetrics.has(metricName)) { - this.updateStatsTableRow_(statsTable, metricName, '(removed)'); - } - } - // Add or update all "metric: value" that have a defined value. - const date = new Date(time); - this.updateStatsTableRow_(statsTable, 'timestamp', date.toLocaleString()); - for (let i = 0; i < statsData.length - 1; i = i + 2) { - this.updateStatsTableRow_(statsTable, statsData[i], statsData[i + 1]); - } - } - - /** - * Update the value column of the stats row of |rowName| to |value|. - * A new row is created is this is the first report of this stats. - * - * @param {!Element} statsTable Which table to update. - * @param {string} rowName The name of the row to update. - * @param {string} value The new value to set. - * @private - */ - updateStatsTableRow_(statsTable, rowName, value) { - const trId = statsTable.id + '-' + rowName; - // Disable getElementById restriction here, since |trId| is not always - // a valid selector. - // eslint-disable-next-line no-restricted-properties - let trElement = document.getElementById(trId); - const activeConnectionClass = 'stats-table-active-connection'; - if (!trElement) { - trElement = document.createElement('tr'); - trElement.id = trId; - statsTable.firstChild.appendChild(trElement); - const item = $('td2-template').content.cloneNode(true); - item.querySelector('td').textContent = rowName; - trElement.appendChild(item); - } - trElement.cells[1].textContent = value; - - // Highlights the table for the active connection. - if (rowName === 'googActiveConnection') { - if (value === true) { - statsTable.parentElement.classList.add(activeConnectionClass); - } else { - statsTable.parentElement.classList.remove(activeConnectionClass); - } - } - } - - /** - * Apply a filter to the stats table - * @param event InputEvent from the filter input field. - * @param container stats table container element. - * @private - */ - filterStats(event, container) { - const filter = event.target.value; - const filters = filter.split(','); - container.childNodes.forEach(node => { - if (node.nodeName !== 'DETAILS') { - return; - } - const statsType = node.attributes['data-statsType']; - if (!filter || filters.includes(statsType) || - filters.find(f => statsType.includes(f))) { - node.style.display = 'block'; - } else { - node.style.display = 'none'; - } - }); - } -} diff --git a/js/calling-tools/tab_view.js b/js/calling-tools/tab_view.js deleted file mode 100644 index daf5449366..0000000000 --- a/js/calling-tools/tab_view.js +++ /dev/null @@ -1,115 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -// Creates a simple object containing the tab head and body elements. -class TabDom { - constructor(h, b) { - this.head = h; - this.body = b; - } -} - -/** - * A TabView provides the ability to create tabs and switch between tabs. It's - * responsible for creating the DOM and managing the visibility of each tab. - * The first added tab is active by default and the others hidden. - */ -export class TabView { - /** - * @param {Element} root The root DOM element containing the tabs. - */ - constructor(root) { - this.root_ = root; - this.ACTIVE_TAB_HEAD_CLASS_ = 'active-tab-head'; - this.ACTIVE_TAB_BODY_CLASS_ = 'active-tab-body'; - this.TAB_HEAD_CLASS_ = 'tab-head'; - this.TAB_BODY_CLASS_ = 'tab-body'; - - /** - * A mapping for an id to the tab elements. - * @type {!Object} - * @private - */ - this.tabElements_ = {}; - - this.headBar_ = null; - this.activeTabId_ = null; - this.initializeHeadBar_(); - } - - /** - * Adds a tab with the specified id and title. - * @param {string} id - * @param {string} title - * @return {!Element} The tab body element. - */ - addTab(id, title) { - if (this.tabElements_[id]) { - throw 'Tab already exists: ' + id; - } - - const head = document.createElement('span'); - head.className = this.TAB_HEAD_CLASS_; - head.textContent = title; - head.title = title; - this.headBar_.appendChild(head); - head.addEventListener('click', this.switchTab_.bind(this, id)); - - const body = document.createElement('div'); - body.className = this.TAB_BODY_CLASS_; - body.id = id; - this.root_.appendChild(body); - - this.tabElements_[id] = new TabDom(head, body); - - if (!this.activeTabId_) { - this.switchTab_(id); - } - return this.tabElements_[id].body; - } - - /** Removes the tab. @param {string} id */ - removeTab(id) { - if (!this.tabElements_[id]) { - return; - } - this.tabElements_[id].head.parentNode.removeChild( - this.tabElements_[id].head); - this.tabElements_[id].body.parentNode.removeChild( - this.tabElements_[id].body); - - delete this.tabElements_[id]; - if (this.activeTabId_ === id) { - this.switchTab_(Object.keys(this.tabElements_)[0]); - } - } - - /** - * Switches the specified tab into view. - * - * @param {string} activeId The id the of the tab that should be switched to - * active state. - * @private - */ - switchTab_(activeId) { - if (this.activeTabId_ && this.tabElements_[this.activeTabId_]) { - this.tabElements_[this.activeTabId_].body.classList.remove( - this.ACTIVE_TAB_BODY_CLASS_); - this.tabElements_[this.activeTabId_].head.classList.remove( - this.ACTIVE_TAB_HEAD_CLASS_); - } - this.activeTabId_ = activeId; - if (this.tabElements_[activeId]) { - this.tabElements_[activeId].body.classList.add( - this.ACTIVE_TAB_BODY_CLASS_); - this.tabElements_[activeId].head.classList.add( - this.ACTIVE_TAB_HEAD_CLASS_); - } - } - - /** Initializes the bar containing the tab heads. */ - initializeHeadBar_() { - this.headBar_ = document.createElement('div'); - this.root_.appendChild(this.headBar_); - this.headBar_.style.textAlign = 'center'; - } -} diff --git a/js/calling-tools/timeline_graph_view.js b/js/calling-tools/timeline_graph_view.js deleted file mode 100644 index 7dbe6e1c21..0000000000 --- a/js/calling-tools/timeline_graph_view.js +++ /dev/null @@ -1,548 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -// Maximum number of labels placed vertically along the sides of the graph. -const MAX_VERTICAL_LABELS = 6; - -// Vertical spacing between labels and between the graph and labels. -const LABEL_VERTICAL_SPACING = 4; -// Horizontal spacing between vertically placed labels and the edges of the -// graph. -const LABEL_HORIZONTAL_SPACING = 3; -// Horizintal spacing between two horitonally placed labels along the bottom -// of the graph. -const LABEL_LABEL_HORIZONTAL_SPACING = 25; - -// Length of ticks, in pixels, next to y-axis labels. The x-axis only has -// one set of labels, so it can use lines instead. -const Y_AXIS_TICK_LENGTH = 10; - -const GRID_COLOR = '#CCC'; -const TEXT_COLOR = '#000'; -const BACKGROUND_COLOR = '#FFF'; - -const MAX_DECIMAL_PRECISION = 3; - -/** - * A TimelineGraphView displays a timeline graph on a canvas element. - */ -export class TimelineGraphView { - constructor(divId, canvasId) { - this.scrollbar_ = {position_: 0, range_: 0}; - - // Disable getElementById restriction here, since |divId| and |canvasId| are - // not always valid selectors. - // eslint-disable-next-line no-restricted-properties - this.graphDiv_ = document.getElementById(divId); - // eslint-disable-next-line no-restricted-properties - this.canvas_ = document.getElementById(canvasId); - - // Set the range and scale of the graph. Times are in milliseconds since - // the Unix epoch. - - // All measurements we have must be after this time. - this.startTime_ = 0; - // The current rightmost position of the graph is always at most this. - this.endTime_ = 1; - - this.graph_ = null; - - // Horizontal scale factor, in terms of milliseconds per pixel. - this.scale_ = 1000; - - // Initialize the scrollbar. - this.updateScrollbarRange_(true); - } - - setScale(scale) { - this.scale_ = scale; - } - - // Returns the total length of the graph, in pixels. - getLength_() { - const timeRange = this.endTime_ - this.startTime_; - // Math.floor is used to ignore the last partial area, of length less - // than this.scale_. - return Math.floor(timeRange / this.scale_); - } - - /** - * Returns true if the graph is scrolled all the way to the right. - */ - graphScrolledToRightEdge_() { - return this.scrollbar_.position_ === this.scrollbar_.range_; - } - - /** - * Update the range of the scrollbar. If |resetPosition| is true, also - * sets the slider to point at the rightmost position and triggers a - * repaint. - */ - updateScrollbarRange_(resetPosition) { - let scrollbarRange = this.getLength_() - this.canvas_.width; - if (scrollbarRange < 0) { - scrollbarRange = 0; - } - - // If we've decreased the range to less than the current scroll position, - // we need to move the scroll position. - if (this.scrollbar_.position_ > scrollbarRange) { - resetPosition = true; - } - - this.scrollbar_.range_ = scrollbarRange; - if (resetPosition) { - this.scrollbar_.position_ = scrollbarRange; - this.repaint(); - } - } - - /** - * Sets the date range displayed on the graph, switches to the default - * scale factor, and moves the scrollbar all the way to the right. - */ - setDateRange(startDate, endDate) { - this.startTime_ = startDate.getTime(); - this.endTime_ = endDate.getTime(); - - // Safety check. - if (this.endTime_ <= this.startTime_) { - this.startTime_ = this.endTime_ - 1; - } - - this.updateScrollbarRange_(true); - } - - /** - * Updates the end time at the right of the graph to be the current time. - * Specifically, updates the scrollbar's range, and if the scrollbar is - * all the way to the right, keeps it all the way to the right. Otherwise, - * leaves the view as-is and doesn't redraw anything. - */ - updateEndDate(opt_date) { - this.endTime_ = opt_date || (new Date()).getTime(); - this.updateScrollbarRange_(this.graphScrolledToRightEdge_()); - } - - getStartDate() { - return new Date(this.startTime_); - } - - /** - * Replaces the current TimelineDataSeries with |dataSeries|. - */ - setDataSeries(dataSeries) { - // Simply recreates the Graph. - this.graph_ = new Graph(); - for (let i = 0; i < dataSeries.length; ++i) { - this.graph_.addDataSeries(dataSeries[i]); - } - this.repaint(); - } - - /** - * Adds |dataSeries| to the current graph. - */ - addDataSeries(dataSeries) { - if (!this.graph_) { - this.graph_ = new Graph(); - } - this.graph_.addDataSeries(dataSeries); - this.repaint(); - } - - /** - * Draws the graph on |canvas_| when visible. - */ - repaint() { - if (this.canvas_.offsetParent === null) { - return; // do not repaint graphs that are not visible. - } - - this.repaintTimerRunning_ = false; - - const width = this.canvas_.width; - let height = this.canvas_.height; - const context = this.canvas_.getContext('2d'); - - // Clear the canvas. - context.fillStyle = BACKGROUND_COLOR; - context.fillRect(0, 0, width, height); - - // Try to get font height in pixels. Needed for layout. - const fontHeightString = context.font.match(/([0-9]+)px/)[1]; - const fontHeight = parseInt(fontHeightString); - - // Safety check, to avoid drawing anything too ugly. - if (fontHeightString.length === 0 || fontHeight <= 0 || - fontHeight * 4 > height || width < 50) { - return; - } - - // Save current transformation matrix so we can restore it later. - context.save(); - - // The center of an HTML canvas pixel is technically at (0.5, 0.5). This - // makes near straight lines look bad, due to anti-aliasing. This - // translation reduces the problem a little. - context.translate(0.5, 0.5); - - // Figure out what time values to display. - let position = this.scrollbar_.position_; - // If the entire time range is being displayed, align the right edge of - // the graph to the end of the time range. - if (this.scrollbar_.range_ === 0) { - position = this.getLength_() - this.canvas_.width; - } - const visibleStartTime = this.startTime_ + position * this.scale_; - - // Make space at the bottom of the graph for the time labels, and then - // draw the labels. - const textHeight = height; - height -= fontHeight + LABEL_VERTICAL_SPACING; - this.drawTimeLabels(context, width, height, textHeight, visibleStartTime); - - // Draw outline of the main graph area. - context.strokeStyle = GRID_COLOR; - context.strokeRect(0, 0, width - 1, height - 1); - - if (this.graph_) { - // Layout graph and have them draw their tick marks. - this.graph_.layout( - width, height, fontHeight, visibleStartTime, this.scale_); - this.graph_.drawTicks(context); - - // Draw the lines of all graphs, and then draw their labels. - this.graph_.drawLines(context); - this.graph_.drawLabels(context); - } - - // Restore original transformation matrix. - context.restore(); - } - - /** - * Draw time labels below the graph. Takes in start time as an argument - * since it may not be |startTime_|, when we're displaying the entire - * time range. - */ - drawTimeLabels(context, width, height, textHeight, startTime) { - // Draw the labels 1 minute apart. - const timeStep = 1000 * 60; - - // Find the time for the first label. This time is a perfect multiple of - // timeStep because of how UTC times work. - let time = Math.ceil(startTime / timeStep) * timeStep; - - context.textBaseline = 'bottom'; - context.textAlign = 'center'; - context.fillStyle = TEXT_COLOR; - context.strokeStyle = GRID_COLOR; - - // Draw labels and vertical grid lines. - while (true) { - const x = Math.round((time - startTime) / this.scale_); - if (x >= width) { - break; - } - const text = (new Date(time)).toLocaleTimeString(); - context.fillText(text, x, textHeight); - context.beginPath(); - context.lineTo(x, 0); - context.lineTo(x, height); - context.stroke(); - time += timeStep; - } - } - - getDataSeriesCount() { - if (this.graph_) { - return this.graph_.dataSeries_.length; - } - return 0; - } - - hasDataSeries(dataSeries) { - if (this.graph_) { - return this.graph_.hasDataSeries(dataSeries); - } - return false; - } -} - -/** - * A Label is the label at a particular position along the y-axis. - */ -class Label { - constructor(height, text) { - this.height = height; - this.text = text; - } -} - -/** - * A Graph is responsible for drawing all the TimelineDataSeries that have - * the same data type. Graphs are responsible for scaling the values, laying - * out labels, and drawing both labels and lines for its data series. - */ -class Graph { - constructor() { - this.dataSeries_ = []; - - // Cached properties of the graph, set in layout. - this.width_ = 0; - this.height_ = 0; - this.fontHeight_ = 0; - this.startTime_ = 0; - this.scale_ = 0; - - // The lowest/highest values adjusted by the vertical label step size - // in the displayed range of the graph. Used for scaling and setting - // labels. Set in layoutLabels. - this.min_ = 0; - this.max_ = 0; - - // Cached text of equally spaced labels. Set in layoutLabels. - this.labels_ = []; - } - - addDataSeries(dataSeries) { - this.dataSeries_.push(dataSeries); - } - - hasDataSeries(dataSeries) { - for (let i = 0; i < this.dataSeries_.length; ++i) { - if (this.dataSeries_[i] === dataSeries) { - return true; - } - } - return false; - } - - /** - * Returns a list of all the values that should be displayed for a given - * data series, using the current graph layout. - */ - getValues(dataSeries) { - if (!dataSeries.isVisible()) { - return null; - } - return dataSeries.getValues(this.startTime_, this.scale_, this.width_); - } - - /** - * Updates the graph's layout. In particular, both the max value and - * label positions are updated. Must be called before calling any of the - * drawing functions. - */ - layout(width, height, fontHeight, startTime, scale) { - this.width_ = width; - this.height_ = height; - this.fontHeight_ = fontHeight; - this.startTime_ = startTime; - this.scale_ = scale; - - // Find largest value. - let max = 0; - let min = 0; - for (let i = 0; i < this.dataSeries_.length; ++i) { - const values = this.getValues(this.dataSeries_[i]); - if (!values) { - continue; - } - for (let j = 0; j < values.length; ++j) { - if (values[j] > max) { - max = values[j]; - } else if (values[j] < min) { - min = values[j]; - } - } - } - - this.layoutLabels_(min, max); - } - - /** - * Lays out labels and sets |max_|/|min_|, taking the time units into - * consideration. |maxValue| is the actual maximum value, and - * |max_| will be set to the value of the largest label, which - * will be at least |maxValue|. Similar for |min_|. - */ - layoutLabels_(minValue, maxValue) { - if (maxValue - minValue < 1024) { - this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION); - return; - } - - // Find appropriate units to use. - const units = ['', 'k', 'M', 'G', 'T', 'P']; - // Units to use for labels. 0 is '1', 1 is K, etc. - // We start with 1, and work our way up. - let unit = 1; - minValue /= 1024; - maxValue /= 1024; - while (units[unit + 1] && maxValue - minValue >= 1024) { - minValue /= 1024; - maxValue /= 1024; - ++unit; - } - - // Calculate labels. - this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION); - - // Append units to labels. - for (let i = 0; i < this.labels_.length; ++i) { - this.labels_[i] += ' ' + units[unit]; - } - - // Convert |min_|/|max_| back to unit '1'. - this.min_ *= Math.pow(1024, unit); - this.max_ *= Math.pow(1024, unit); - } - - /** - * Same as layoutLabels_, but ignores units. |maxDecimalDigits| is the - * maximum number of decimal digits allowed. The minimum allowed - * difference between two adjacent labels is 10^-|maxDecimalDigits|. - */ - layoutLabelsBasic_(minValue, maxValue, maxDecimalDigits) { - this.labels_ = []; - const range = maxValue - minValue; - // No labels if the range is 0. - if (range === 0) { - this.min_ = this.max_ = maxValue; - return; - } - - // The maximum number of equally spaced labels allowed. |fontHeight_| - // is doubled because the top two labels are both drawn in the same - // gap. - const minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING; - - // The + 1 is for the top label. - let maxLabels = 1 + this.height_ / minLabelSpacing; - if (maxLabels < 2) { - maxLabels = 2; - } else if (maxLabels > MAX_VERTICAL_LABELS) { - maxLabels = MAX_VERTICAL_LABELS; - } - - // Initial try for step size between consecutive labels. - let stepSize = Math.pow(10, -maxDecimalDigits); - // Number of digits to the right of the decimal of |stepSize|. - // Used for formatting label strings. - let stepSizeDecimalDigits = maxDecimalDigits; - - // Pick a reasonable step size. - while (true) { - // If we use a step size of |stepSize| between labels, we'll need: - // - // Math.ceil(range / stepSize) + 1 - // - // labels. The + 1 is because we need labels at both at 0 and at - // the top of the graph. - - // Check if we can use steps of size |stepSize|. - if (Math.ceil(range / stepSize) + 1 <= maxLabels) { - break; - } - // Check |stepSize| * 2. - if (Math.ceil(range / (stepSize * 2)) + 1 <= maxLabels) { - stepSize *= 2; - break; - } - // Check |stepSize| * 5. - if (Math.ceil(range / (stepSize * 5)) + 1 <= maxLabels) { - stepSize *= 5; - break; - } - stepSize *= 10; - if (stepSizeDecimalDigits > 0) { - --stepSizeDecimalDigits; - } - } - - // Set the min/max so it's an exact multiple of the chosen step size. - this.max_ = Math.ceil(maxValue / stepSize) * stepSize; - this.min_ = Math.floor(minValue / stepSize) * stepSize; - - // Create labels. - for (let label = this.max_; label >= this.min_; label -= stepSize) { - this.labels_.push(label.toFixed(stepSizeDecimalDigits)); - } - } - - /** - * Draws tick marks for each of the labels in |labels_|. - */ - drawTicks(context) { - const x1 = this.width_ - 1; - const x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH; - - context.fillStyle = GRID_COLOR; - context.beginPath(); - for (let i = 1; i < this.labels_.length - 1; ++i) { - // The rounding is needed to avoid ugly 2-pixel wide anti-aliased - // lines. - const y = Math.round(this.height_ * i / (this.labels_.length - 1)); - context.moveTo(x1, y); - context.lineTo(x2, y); - } - context.stroke(); - } - - /** - * Draws a graph line for each of the data series. - */ - drawLines(context) { - // Factor by which to scale all values to convert them to a number from - // 0 to height - 1. - let scale = 0; - const bottom = this.height_ - 1; - if (this.max_) { - scale = bottom / (this.max_ - this.min_); - } - - // Draw in reverse order, so earlier data series are drawn on top of - // subsequent ones. - for (let i = this.dataSeries_.length - 1; i >= 0; --i) { - const values = this.getValues(this.dataSeries_[i]); - if (!values) { - continue; - } - context.strokeStyle = this.dataSeries_[i].getColor(); - context.beginPath(); - for (let x = 0; x < values.length; ++x) { - // The rounding is needed to avoid ugly 2-pixel wide anti-aliased - // horizontal lines. - context.lineTo(x, bottom - Math.round((values[x] - this.min_) * scale)); - } - context.stroke(); - } - } - - /** - * Draw labels in |labels_|. - */ - drawLabels(context) { - if (this.labels_.length === 0) { - return; - } - const x = this.width_ - LABEL_HORIZONTAL_SPACING; - - // Set up the context. - context.fillStyle = TEXT_COLOR; - context.textAlign = 'right'; - - // Draw top label, which is the only one that appears below its tick - // mark. - context.textBaseline = 'top'; - context.fillText(this.labels_[0], x, 0); - - // Draw all the other labels. - context.textBaseline = 'bottom'; - const step = (this.height_ - 1) / (this.labels_.length - 1); - for (let i = 1; i < this.labels_.length; ++i) { - context.fillText(this.labels_[i], x, step * i); - } - } -} diff --git a/js/calling-tools/user_media_table.js b/js/calling-tools/user_media_table.js deleted file mode 100644 index 9a1da6fdf8..0000000000 --- a/js/calling-tools/user_media_table.js +++ /dev/null @@ -1,178 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -import {$} from './util.js'; -const USER_MEDIA_TAB_ID = 'user-media-tab-id'; - -/** - * A helper function for appending a child element to |parent|. - * - * @param {!Element} parent The parent element. - * @param {string} tag The child element tag. - * @param {string} text The textContent of the new DIV. - * @return {!Element} the new DIV element. - */ - function appendChildWithText(parent, tag, text) { - const child = document.createElement(tag); - child.textContent = text; - parent.appendChild(child); - return child; -} - -export class UserMediaTable { - /** - * @param {Object} tabView the TabView object to add the user media tab to. - */ - constructor(tabView) { - this.tabView = tabView; - } - - /** - * Populate the tab view with a getUserMedia/getDisplayMedia tab. - */ - createTab() { - const container = this.tabView.addTab(USER_MEDIA_TAB_ID, - 'getUserMedia/getDisplayMedia'); - // Create the filter input field and label. - appendChildWithText(container, 'label', 'Filter by origin including '); - const input = document.createElement('input'); - input.size = 30; - input.oninput = this.filterUserMedia.bind(this); - container.appendChild(input); - } - - /** - * Apply a filter to the user media table. - * @param event InputEvent from the filter input field. - * @private - */ - filterUserMedia(event) { - const filter = event.target.value; - const requests = $(USER_MEDIA_TAB_ID).childNodes; - for (let i = 0; i < requests.length; ++i) { - if (!requests[i]['data-origin']) { - continue; - } - if (requests[i]['data-origin'].includes(filter)) { - requests[i].style.display = 'block'; - } else { - requests[i].style.display = 'none'; - } - } - } - - /** - * Adds a getUserMedia/getDisplayMedia request. - * @param {!Object} data The object containing rid {number}, pid {number}, - * origin {string}, request_id {number}, request_type {string}, - * audio {string}, video {string}. - */ - addMedia(data) { - if (!$(USER_MEDIA_TAB_ID)) { - this.createTab(); - } - - const requestDiv = document.createElement('div'); - requestDiv.className = 'user-media-request-div-class'; - requestDiv.id = ['gum', data.rid, data.pid, data.request_id].join('-'); - requestDiv['data-rid'] = data.rid; - requestDiv['data-origin'] = data.origin; - // Insert new getUserMedia calls at the top. - $(USER_MEDIA_TAB_ID).insertBefore(requestDiv, - $(USER_MEDIA_TAB_ID).firstChild); - - appendChildWithText(requestDiv, 'div', 'Caller origin: ' + data.origin); - appendChildWithText(requestDiv, 'div', 'Caller process id: ' + data.pid); - - const el = appendChildWithText(requestDiv, 'span', - data.request_type + ' call'); - el.style.fontWeight = 'bold'; - appendChildWithText(el, 'div', 'Time: ' + - (new Date(data.timestamp).toTimeString())) - .style.fontWeight = 'normal'; - if (data.audio !== undefined) { - appendChildWithText(el, 'div', 'Audio constraints: ' + - (data.audio || 'true')) - .style.fontWeight = 'normal'; - } - if (data.video !== undefined) { - appendChildWithText(el, 'div', 'Video constraints: ' + - (data.video || 'true')) - .style.fontWeight = 'normal'; - } - } - - /** - * Update a getUserMedia/getDisplayMedia request with a result or error. - * - * @param {!Object} data The object containing rid {number}, pid {number}, - * request_id {number}, request_type {string}. - * For results there is also the - * stream_id {string}, audio_track_info {string} and - * video_track_info {string}. - * For errors the error {string} and - * error_message {string} fields are set. - */ - updateMedia(data) { - if (!$(USER_MEDIA_TAB_ID)) { - this.createTab(); - } - - const requestDiv = document.getElementById( - ['gum', data.rid, data.pid, data.request_id].join('-')); - if (!requestDiv) { - console.error('Could not update ' + data.request_type + ' request', data); - return; - } - - if (data.error) { - const el = appendChildWithText(requestDiv, 'span', 'Error'); - el.style.fontWeight = 'bold'; - appendChildWithText(el, 'div', 'Time: ' + - (new Date(data.timestamp).toTimeString())) - .style.fontWeight = 'normal'; - appendChildWithText(el, 'div', 'Error: ' + data.error) - .style.fontWeight = 'normal'; - appendChildWithText(el, 'div', 'Error message: ' + data.error_message) - .style.fontWeight = 'normal'; - return; - } - - const el = appendChildWithText(requestDiv, 'span', - data.request_type + ' result'); - el.style.fontWeight = 'bold'; - appendChildWithText(el, 'div', 'Time: ' + - (new Date(data.timestamp).toTimeString())) - .style.fontWeight = 'normal'; - appendChildWithText(el, 'div', 'Stream id: ' + data.stream_id) - .style.fontWeight = 'normal'; - if (data.audio_track_info) { - appendChildWithText(el, 'div', 'Audio track: ' + data.audio_track_info) - .style.fontWeight = 'normal'; - } - if (data.video_track_info) { - appendChildWithText(el, 'div', 'Video track: ' + data.video_track_info) - .style.fontWeight = 'normal'; - } - } - - /** - * Removes the getUserMedia/getDisplayMedia requests from the specified |rid|. - * - * @param {!Object} data The object containing rid {number}, the render id. - */ - removeMediaForRenderer(data) { - const requests = $(USER_MEDIA_TAB_ID).childNodes; - for (let i = 0; i < requests.length; ++i) { - if (!requests[i]['data-origin']) { - continue; - } - if (requests[i]['data-rid'] === data.rid) { - $(USER_MEDIA_TAB_ID).removeChild(requests[i]); - } - } - // Remove the tab when only the search field and its label are left. - if ($(USER_MEDIA_TAB_ID).childNodes.length === 2) { - this.tabView.removeTab(USER_MEDIA_TAB_ID); - } - } -} diff --git a/js/calling-tools/util.js b/js/calling-tools/util.js deleted file mode 100644 index f75d8e93e3..0000000000 --- a/js/calling-tools/util.js +++ /dev/null @@ -1,88 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -import {assert} from "./assert.js"; -export function $(id) { - const el = document.querySelector(`#${id}`); - if (el) { - assert(el instanceof HTMLElement); - return el - } - return null -} -export function getRequiredElement(id) { - const el = document.querySelector(`#${id}`); - assert(el); - assert(el instanceof HTMLElement); - return el -} -export function getDeepActiveElement() { - let a = document.activeElement; - while (a && a.shadowRoot && a.shadowRoot.activeElement) { - a = a.shadowRoot.activeElement - } - return a -} -export function isRTL() { - return document.documentElement.dir === "rtl" -} -export function appendParam(url, key, value) { - const param = encodeURIComponent(key) + "=" + encodeURIComponent(value); - if (url.indexOf("?") === -1) { - return url + "?" + param - } - return url + "&" + param -} -export function ensureTransitionEndEvent(el, timeOut) { - if (timeOut === undefined) { - const style = getComputedStyle(el); - timeOut = parseFloat(style.transitionDuration) * 1e3; - timeOut += 50 - } - let fired = false; - el.addEventListener("transitionend", (function f() { - el.removeEventListener("transitionend", f); - fired = true - } - )); - window.setTimeout((function() { - if (!fired) { - el.dispatchEvent(new CustomEvent("transitionend",{ - bubbles: true, - composed: true - })) - } - } - ), timeOut) -} -export function htmlEscape(original) { - return original.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'") -} -export function quoteString(str) { - return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1") -} -export function listenOnce(target, eventNames, callback) { - const eventNamesArray = Array.isArray(eventNames) ? eventNames : eventNames.split(/ +/); - const removeAllAndCallCallback = function(event) { - eventNamesArray.forEach((function(eventName) { - target.removeEventListener(eventName, removeAllAndCallCallback, false) - } - )); - return callback(event) - }; - eventNamesArray.forEach((function(eventName) { - target.addEventListener(eventName, removeAllAndCallCallback, false) - } - )) -} -export function hasKeyModifiers(e) { - return !!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) -} -export function isUndoKeyboardEvent(event) { - if (event.key !== "z") { - return false - } - const excludedModifiers = [event.altKey, event.shiftKey, event.ctrlKey]; - let targetModifier = event.ctrlKey; - targetModifier = event.metaKey; - return targetModifier && !excludedModifiers.some((modifier=>modifier)) -} diff --git a/js/calling-tools/webrtc_internals.dom.js b/js/calling-tools/webrtc_internals.dom.js deleted file mode 100644 index afae289114..0000000000 --- a/js/calling-tools/webrtc_internals.dom.js +++ /dev/null @@ -1,509 +0,0 @@ -// Derived from Chromium WebRTC Internals Dashboard - see Acknowledgements for full license details - -import {$} from './util.js'; - -import {createIceCandidateGrid, updateIceCandidateGrid} from './candidate_grid.js'; -import {MAX_STATS_DATA_POINT_BUFFER_SIZE} from './data_series.js'; -import {DumpCreator, peerConnectionDataStore, userMediaRequests} from './dump_creator.js'; -import {PeerConnectionUpdateTable} from './peer_connection_update_table.js'; -import {drawSingleReport, removeStatsReportGraphs} from './stats_graph_helper.js'; -import {StatsRatesCalculator, StatsReport} from './stats_rates_calculator.js'; -import {StatsTable} from './stats_table.js'; -import {TabView} from './tab_view.js'; -import {UserMediaTable} from './user_media_table.js'; -import '../../ts/windows/sandboxedInit.dom.js'; - -let tabView = null; -let peerConnectionUpdateTable = null; -let statsTable = null; -let userMediaTable = null; -let dumpCreator = null; -let requestedStatsInterval = 2000; - -// Start Signal Change -let stats_queue = []; - -function onRtcStatsReport(event, report) { - const rs = JSON.parse(report.reportJson); - const mungedReports = rs.map(r => ({ - id: r.id, - type: r.type, - stats: { - timestamp: r.timestamp / 1000, - values: Object - .keys(r) - .filter(k => !['id', 'type', 'timestamp'].includes(k)) - .reduce((acc, k) => { - acc.push(k, r[k]); - return acc; - }, []), - }, - })); - - // fake since we can only have 1 call going at a time - // pid should be related to call ID - // lid is only one peer connection per call currently - stats_queue.push( - { - pid: 100, - rid: report.conversationId, - lid: report.callId, - reports: mungedReports, - } - ) -} -window.Signal.CallingToolsProps.onRtcStatsReport(onRtcStatsReport); -// End Signal Change - -const searchParameters = new URLSearchParams(window.location.search); - -/** Maps from id (see getPeerConnectionId) to StatsRatesCalculator. */ -const statsRatesCalculatorById = new Map(); - -/** A simple class to store the updates and stats data for a peer connection. */ - /** @constructor */ -class PeerConnectionRecord { - constructor() { - /** @private */ - this.record_ = { - pid: -1, - constraints: {}, - rtcConfiguration: [], - stats: {}, - updateLog: [], - url: '', - }; - } - - /** @override */ - toJSON() { - return this.record_; - } - - /** - * Adds the initialization info of the peer connection. - * @param {number} pid The pid of the process hosting the peer connection. - * @param {string} url The URL of the web page owning the peer connection. - * @param {Array} rtcConfiguration - * @param {!Object} constraints Media constraints. - */ - initialize(pid, url, rtcConfiguration, constraints) { - this.record_.pid = pid; - this.record_.url = url; - this.record_.rtcConfiguration = rtcConfiguration; - this.record_.constraints = constraints; - } - - resetStats() { - this.record_.stats = {}; - } - - /** - * @param {string} dataSeriesId The TimelineDataSeries identifier. - * @return {!TimelineDataSeries} - */ - getDataSeries(dataSeriesId) { - return this.record_.stats[dataSeriesId]; - } - - /** - * @param {string} dataSeriesId The TimelineDataSeries identifier. - * @param {!TimelineDataSeries} dataSeries The TimelineDataSeries to set to. - */ - setDataSeries(dataSeriesId, dataSeries) { - this.record_.stats[dataSeriesId] = dataSeries; - } - - /** - * @param {!Object} update The object contains keys "time", "type", and - * "value". - */ - addUpdate(update) { - const time = new Date(parseFloat(update.time)); - this.record_.updateLog.push({ - time: time.toLocaleString(), - type: update.type, - value: update.value, - }); - } -} - -function addMedia(data) { - userMediaRequests.push(data); - userMediaTable.addMedia(data) -} - -function updateMedia(data) { - userMediaRequests.push(data); - userMediaTable.updateMedia(data); - -} - -function removeMediaForRenderer(data) { - for (let i = userMediaRequests.length - 1; i >= 0; --i) { - if (userMediaRequests[i].rid === data.rid) { - userMediaRequests.splice(i, 1); - } - } - userMediaTable.removeMediaForRenderer(data); -} - -function setRtcStatsInterval() { - window.Signal.CallingToolsProps.setRtcStatsInterval(requestedStatsInterval); -} - -function initialize() { - const { i18n } = window.SignalContext; - let placeholderTitle = i18n('icu:callingDeveloperTools'); - let placeholderDescription = i18n('icu:callingDeveloperToolsDescription'); - $('placeholder-title').innerText = placeholderTitle; - $('placeholder-description').innerText = placeholderDescription; - - dumpCreator = new DumpCreator($('content-root')); - - tabView = new TabView($('content-root')); - peerConnectionUpdateTable = new PeerConnectionUpdateTable(); - statsTable = new StatsTable(); - userMediaTable = new UserMediaTable(tabView, userMediaRequests); - - let processStatsInterval = 1000; - window.setInterval(processStats, processStatsInterval); - setRtcStatsInterval(2000); -} -document.addEventListener('DOMContentLoaded', initialize); - -/** - * Sends a request to the browser to get peer connection statistics from the - * standard getStats() API (promise-based). - */ -function processStats() { - // Start Signal Change - for(let i = 0; i < 10 && stats_queue.length > 0; i++) { - addStandardStats(stats_queue.shift()); - } - // End Signal Change -} - -/** - * A helper function for getting a peer connection element id. - * - * @param {!Object} data The object containing the rid and lid of the - * peer connection. - * @return {string} The peer connection element id. - */ -function getPeerConnectionId(data) { - return data.rid + '-' + data.lid; -} - -/** - * A helper function for appending a child element to |parent|. - * - * @param {!Element} parent The parent element. - * @param {string} tag The child element tag. - * @param {string} text The textContent of the new DIV. - * @return {!Element} the new DIV element. - */ -function appendChildWithText(parent, tag, text) { - const child = document.createElement(tag); - child.textContent = text; - parent.appendChild(child); - return child; -} - -/** - * Helper for adding a peer connection update. - * - * @param {Element} peerConnectionElement - * @param {!PeerConnectionUpdateEntry} update The peer connection update data. - */ -function addPeerConnectionUpdate(peerConnectionElement, update) { - - peerConnectionUpdateTable.addPeerConnectionUpdate( - peerConnectionElement, update); - peerConnectionDataStore[peerConnectionElement.id].addUpdate(update); -} - - -/** Browser message handlers. */ - - -/** - * Removes all information about a peer connection. - * Use ?keepRemovedConnections url parameter to prevent the removal. - * - * @param {!Object} data The object containing the rid and lid of a peer - * connection. - */ -function removePeerConnection(data) { - // Disable getElementById restriction here, since |getPeerConnectionId| does - // not return valid selectors. - // eslint-disable-next-line no-restricted-properties - - const element = document.getElementById(getPeerConnectionId(data)); - if (element && !searchParameters.has('keepRemovedConnections')) { - delete peerConnectionDataStore[element.id]; - tabView.removeTab(element.id); - } -} - -/** - * Adds a peer connection. - * - * @param {!Object} data The object containing the rid, lid, pid, url, - * rtcConfiguration, and constraints of a peer connection. - */ -function addPeerConnection(data) { - const id = getPeerConnectionId(data); - - if (!peerConnectionDataStore[id]) { - peerConnectionDataStore[id] = new PeerConnectionRecord(); - } - peerConnectionDataStore[id].initialize( - data.pid, data.url, data.rtcConfiguration, data.constraints); - - // Disable getElementById restriction here, since |id| is not always - // a valid selector. - // eslint-disable-next-line no-restricted-properties - let peerConnectionElement = document.getElementById(id); - if (!peerConnectionElement) { - const details = `[ rid: ${data.rid}, lid: ${data.lid}, pid: ${data.pid} ]`; - peerConnectionElement = tabView.addTab(id, data.url + " " + details); - } - - const p = document.createElement('p'); - appendChildWithText(p, 'span', data.url); - appendChildWithText(p, 'span', ', '); - appendChildWithText(p, 'span', data.rtcConfiguration); - if (data.constraints !== '') { - appendChildWithText(p, 'span', ', '); - appendChildWithText(p, 'span', data.constraints); - } - peerConnectionElement.appendChild(p); - - // Show deprecation notices as a list. - // Note: data.rtcConfiguration is not in JSON format and may - // not be defined in tests. - const deprecationNotices = document.createElement('ul'); - if (data.rtcConfiguration) { - deprecationNotices.className = 'peerconnection-deprecations'; - } - peerConnectionElement.appendChild(deprecationNotices); - - const iceConnectionStates = document.createElement('div'); - iceConnectionStates.textContent = 'ICE connection state: new'; - iceConnectionStates.className = 'iceconnectionstate'; - peerConnectionElement.appendChild(iceConnectionStates); - - const connectionStates = document.createElement('div'); - connectionStates.textContent = 'Connection state: new'; - connectionStates.className = 'connectionstate'; - peerConnectionElement.appendChild(connectionStates); - - const signalingStates = document.createElement('div'); - signalingStates.textContent = 'Signaling state: new'; - signalingStates.className = 'signalingstate'; - peerConnectionElement.appendChild(signalingStates); - - const candidatePair = document.createElement('div'); - candidatePair.textContent = 'ICE Candidate pair: '; - candidatePair.className = 'candidatepair'; - candidatePair.appendChild(document.createElement('span')); - peerConnectionElement.appendChild(candidatePair); - - createIceCandidateGrid(peerConnectionElement); - return peerConnectionElement; -} - - -/** - * Adds a peer connection update. - * - * @param {!PeerConnectionUpdateEntry} data The peer connection update data. - */ -function updatePeerConnection(data) { - // Disable getElementById restriction here, since |getPeerConnectionId| does - // not return valid selectors. - const peerConnectionElement = - // eslint-disable-next-line no-restricted-properties - document.getElementById(getPeerConnectionId(data)); - addPeerConnectionUpdate(peerConnectionElement, data); -} - - -/** - * Adds the information of all peer connections created so far. - * - * @param {Array} data An array of the information of all peer - * connections. Each array item contains rid, lid, pid, url, - * rtcConfiguration, constraints, and an array of updates as the log. - */ -function updateAllPeerConnections(data) { - for (let i = 0; i < data.length; ++i) { - const peerConnection = addPeerConnection(data[i]); - - const log = data[i].log; - if (!log) { - continue; - } - for (let j = 0; j < log.length; ++j) { - addPeerConnectionUpdate(peerConnection, log[j]); - } - } - processStats(); -} - -/** - * Handles the report of stats originating from the standard getStats() API. - * - * @param {!Object} data The object containing rid, lid, and reports, where - * reports is an array of stats reports. Each report contains id, type, - * and stats, where stats is the object containing timestamp and values, - * which is an array of strings, whose even index entry is the name of the - * stat, and the odd index entry is the value. - */ -function addStandardStats(data) { - // Disable getElementById restriction here, since |getPeerConnectionId| does - // not return valid selectors. - // eslint-disable-next-line no-restricted-properties - let peerConnectionElement = - // eslint-disable-next-line no-restricted-properties - document.getElementById(getPeerConnectionId(data)); - if (!peerConnectionElement) { - // fake the add peer event - peerConnectionElement = addPeerConnection({ - connected: false, - isOpen: true, - lid: data.lid, - rid: data.rid, - rtcConfiguration: "{ iceServers: [], iceTransportPolicy: all, bundlePolicy: balanced, rtcpMuxPolicy: require, iceCandidatePoolSize: 0 }", - url: "groupcall" - }); - // eslint-disable-next-line no-restricted-properties - if(!peerConnectionElement) { - console.error("Failed to create peerConnection Element"); - } - } - - const pcId = getPeerConnectionId(data); - let statsRatesCalculator = statsRatesCalculatorById.get(pcId); - if (!statsRatesCalculator) { - statsRatesCalculator = new StatsRatesCalculator(); - statsRatesCalculatorById.set(pcId, statsRatesCalculator); - } - // This just changes the reports from their array format into an object format, then adds it to statsByAdd - const r = StatsReport.fromInternalsReportList(data.reports); - statsRatesCalculator.addStatsReport(r); - data.reports = statsRatesCalculator.currentReport.toInternalsReportList(); - for (let i = 0; i < data.reports.length; ++i) { - const report = data.reports[i]; - statsTable.addStatsReport(peerConnectionElement, report); - drawSingleReport(peerConnectionElement, report); - } - // Determine currently connected candidate pair. - const stats = r.statsById; - - let activeCandidatePair = null; - let remoteCandidate = null; - let localCandidate = null; - - // Get the first active candidate pair. This ignores the rare case of - // non-bundled connections. - stats.forEach(report => { - if (report.type === 'transport' && !activeCandidatePair) { - activeCandidatePair = stats.get(report.selectedCandidatePairId); - } - }); - - const candidateElement = peerConnectionElement - .getElementsByClassName('candidatepair')[0].firstElementChild; - if (activeCandidatePair) { - if (activeCandidatePair.remoteCandidateId) { - remoteCandidate = stats.get(activeCandidatePair.remoteCandidateId); - } - if (activeCandidatePair.localCandidateId) { - localCandidate = stats.get(activeCandidatePair.localCandidateId); - } - candidateElement.innerText = ''; - if (localCandidate && remoteCandidate) { - if (localCandidate.address && - localCandidate.address.indexOf(':') !== -1) { - // Show IPv6 in [] - candidateElement.innerText +='[' + localCandidate.address + ']'; - } else { - candidateElement.innerText += localCandidate.address || '(not set)'; - } - candidateElement.innerText += ':' + localCandidate.port + ' <=> '; - - if (remoteCandidate.address && - remoteCandidate.address.indexOf(':') !== -1) { - // Show IPv6 in [] - candidateElement.innerText +='[' + remoteCandidate.address + ']'; - } else { - candidateElement.innerText += remoteCandidate.address || '(not set)'; - } - candidateElement.innerText += ':' + remoteCandidate.port; - } - // Mark active local-candidate, remote candidate and candidate pair - // bold in the table. - // Disable getElementById restriction here, since |peerConnectionElement| - // doesn't always have a valid selector ID. - const statsContainer = - // eslint-disable-next-line no-restricted-properties - document.getElementById(peerConnectionElement.id + '-table-container'); - const activeConnectionClass = 'stats-table-active-connection'; - statsContainer.childNodes.forEach(node => { - if (node.nodeName !== 'DETAILS' || !node.children[1]) { - return; - } - const ids = [ - peerConnectionElement.id + '-table-' + activeCandidatePair.id, - peerConnectionElement.id + '-table-' + localCandidate.id, - peerConnectionElement.id + '-table-' + remoteCandidate.id, - ]; - if (ids.includes(node.children[1].id)) { - node.firstElementChild.classList.add(activeConnectionClass); - } else { - node.firstElementChild.classList.remove(activeConnectionClass); - } - }); - // Mark active candidate-pair graph bold. - const statsGraphContainers = peerConnectionElement - .getElementsByClassName('stats-graph-container'); - for (let i = 0; i < statsGraphContainers.length; i++) { - const node = statsGraphContainers[i]; - if (node.nodeName !== 'DETAILS') { - continue; - } - if (!node.id.startsWith(pcId + '-candidate-pair')) { - continue; - } - if (node.id === pcId + '-candidate-pair-' + activeCandidatePair.id - + '-graph-container') { - node.firstElementChild.classList.add(activeConnectionClass); - } else { - node.firstElementChild.classList.remove(activeConnectionClass); - } - } - } else { - candidateElement.innerText = '(not connected)'; - } - - updateIceCandidateGrid(peerConnectionElement, r.statsById); -} - -/** - * Notification that the audio debug recordings file selection dialog was - * cancelled, i.e. recordings have not been enabled. - */ -function audioDebugRecordingsFileSelectionCancelled() { - dumpCreator.clearAudioDebugRecordingsCheckbox(); -} - - -/** - * Notification that the event log recordings file selection dialog was - * cancelled, i.e. recordings have not been enabled. - */ -function eventLogRecordingsFileSelectionCancelled() { - dumpCreator.clearEventLogRecordingsCheckbox(); -} diff --git a/package.json b/package.json index 981a07ffa7..1078f968cf 100644 --- a/package.json +++ b/package.json @@ -590,7 +590,6 @@ "screenShare.html", "settings.html", "permissions_popup.html", - "calling_tools.html", "debug_log.html", "loading.html", { diff --git a/scripts/esbuild.js b/scripts/esbuild.js index be9fc810c3..4ed3b2ae9e 100644 --- a/scripts/esbuild.js +++ b/scripts/esbuild.js @@ -182,13 +182,6 @@ async function sandboxedEnv() { path.join(ROOT_DIR, 'ts', 'windows', 'loading', 'start.dom.ts'), path.join(ROOT_DIR, 'ts', 'windows', 'permissions', 'app.dom.tsx'), path.join(ROOT_DIR, 'ts', 'windows', 'screenShare', 'app.dom.tsx'), - path.join( - ROOT_DIR, - 'ts', - 'windows', - 'calling-tools', - 'webrtc_internals.dom.ts' - ), ], }, preloadConfig: { @@ -205,13 +198,6 @@ async function sandboxedEnv() { 'permissions', 'preload.preload.ts' ), - path.join( - ROOT_DIR, - 'ts', - 'windows', - 'calling-tools', - 'preload.preload.ts' - ), path.join( ROOT_DIR, 'ts', diff --git a/scripts/generate-acknowledgments.js b/scripts/generate-acknowledgments.js index 1f38b5efd2..c609cf9b67 100644 --- a/scripts/generate-acknowledgments.js +++ b/scripts/generate-acknowledgments.js @@ -142,42 +142,6 @@ async function main() { } ); - const markdownForChromiumDashboard = [ - '## Chromium WebRTC Internals Dashboard', - '', - [ - 'Copyright 2015 The Chromium Authors', - '', - 'Redistribution and use in source and binary forms, with or without', - 'modification, are permitted provided that the following conditions are', - 'met:', - '', - ' * Redistributions of source code must retain the above copyright', - 'notice, this list of conditions and the following disclaimer.', - ' * Redistributions in binary form must reproduce the above', - 'copyright notice, this list of conditions and the following disclaimer', - 'in the documentation and/or other materials provided with the', - 'distribution.', - ' * Neither the name of Google LLC nor the names of its', - 'contributors may be used to endorse or promote products derived from', - 'this software without specific prior written permission.', - '', - 'THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS', - '"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT', - 'LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR', - 'A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT', - 'OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,', - 'SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT', - 'LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,', - 'DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY', - 'THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT', - '(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE', - 'OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.', - ] - .map(line => `\t${line}`) - .join('\n'), - ].join('\n'); - const unformattedOutput = [ '', '', @@ -186,7 +150,6 @@ async function main() { 'Signal Desktop makes use of the following open source projects.', '', markdownsForDependency.join('\n\n'), - markdownForChromiumDashboard, '', '## Kyber Patent License', '', diff --git a/ts/services/calling.preload.ts b/ts/services/calling.preload.ts index fcaa4530e9..d9c8a3883d 100644 --- a/ts/services/calling.preload.ts +++ b/ts/services/calling.preload.ts @@ -211,8 +211,6 @@ const CALL_QUALITY_SURVEY_DELAY = 2.5 * durations.SECOND; const ICE_SERVER_IS_IP_LIKE = /(turn|turns|stun):[.\d]+/; -const MAX_CALL_DEBUG_STATS_TABS = 5; - // We send group call update messages to tell other clients to peek, which triggers // notifications, timeline messages, big green "Join" buttons, and so on. This enum // represents the three possible states we can be in. This helps ensure that we don't @@ -536,8 +534,6 @@ export class CallingClass { #lastMediaDeviceSettings?: MediaDeviceSettings; #deviceReselectionTimer?: NodeJS.Timeout; #callsLookup: { [key: string]: Call | GroupCall }; - #currentRtcStatsInterval: number | null = null; - #callDebugNumber: number = 0; #cameraEnabled: boolean = false; @@ -581,7 +577,6 @@ export class CallingClass { this.#handleSendCallMessageToGroup.bind(this); RingRTC.handleGroupCallRingUpdate = this.#handleGroupCallRingUpdate.bind(this); - RingRTC.handleRtcStatsReport = this.#handleRtcStatsReport.bind(this); this.#attemptToGiveOurServiceIdToRingRtc(); window.Whisper.events.on('userChanged', () => { @@ -591,12 +586,6 @@ export class CallingClass { ipcRenderer.on('stop-screen-share', () => { reduxInterface.cancelPresenting(); }); - ipcRenderer.on( - 'calling:set-rtc-stats-interval', - (_, intervalMillis: number | null) => { - this.setAllRtcStatsInterval(intervalMillis); - } - ); drop(this.#cleanExpiredGroupCallRingsAndLoop()); drop(this.cleanupStaleRingingCalls()); @@ -619,16 +608,6 @@ export class CallingClass { }); } - #maybeUpdateRtcLogging(groupCall: GroupCall): void { - if (!this.#currentRtcStatsInterval) { - return; - } - - groupCall.setRtcStatsInterval(this.#currentRtcStatsInterval); - this.#callDebugNumber = - (this.#callDebugNumber + 1) % MAX_CALL_DEBUG_STATS_TABS; - } - #attemptToGiveOurServiceIdToRingRtc(): void { const ourAci = itemStorage.user.getAci(); if (!ourAci) { @@ -1439,7 +1418,6 @@ export class CallingClass { outerGroupCall.connect(); - this.#maybeUpdateRtcLogging(outerGroupCall); this.#syncGroupCallToRedux(conversationId, outerGroupCall, CallMode.Group); return outerGroupCall; @@ -1502,7 +1480,6 @@ export class CallingClass { outerGroupCall.connect(); - this.#maybeUpdateRtcLogging(outerGroupCall); this.#syncGroupCallToRedux(roomId, outerGroupCall, CallMode.Adhoc); return outerGroupCall; @@ -2265,27 +2242,6 @@ export class CallingClass { groupCall.react(value); } - // configures how often call stats are computed - public setAllRtcStatsInterval(intervalMillis: number | null): void { - if (this.#currentRtcStatsInterval === intervalMillis) { - return; - } - this.#currentRtcStatsInterval = intervalMillis; - - // GroupCall.setRtcStatsInterval resets to the default when interval == 0 - // so set it to 0 when intervalMillis is undefined - const statsInterval = intervalMillis ?? 0; - - for (const conversationId of Object.keys(this.#callsLookup)) { - const groupCall = this.#getGroupCall(conversationId); - if (!groupCall) { - continue; - } - log.info('Setting rtc stats interval:', conversationId, statsInterval); - groupCall.setRtcStatsInterval(statsInterval); - } - } - #syncGroupCallToRedux( conversationId: string, groupCall: GroupCall, @@ -3759,18 +3715,6 @@ export class CallingClass { } } - async #handleRtcStatsReport(reportJson: string) { - // assumes one active call - const conversationId = Object.keys(this.#callsLookup)[0] ?? ''; - const callId = this.#callDebugNumber; - - ipcRenderer.send('calling:rtc-stats-report', { - conversationId, - callId, - reportJson, - }); - } - async #handleSendHttpRequest( requestId: number, url: string, diff --git a/ts/test-node/app/menu_test.node.ts b/ts/test-node/app/menu_test.node.ts index af6ec20195..c50bd428b3 100644 --- a/ts/test-node/app/menu_test.node.ts +++ b/ts/test-node/app/menu_test.node.ts @@ -24,7 +24,6 @@ const setupAsNewDevice = stub(); const setupAsStandalone = stub(); const showAbout = stub(); const showDebugLog = stub(); -const showCallingDevTools = stub(); const showKeyboardShortcuts = stub(); const showSettings = stub(); const showWindow = stub(); @@ -74,7 +73,6 @@ const getExpectedViewMenu = (): MenuItemConstructorOptions => ({ { label: 'Debug Log', click: showDebugLog }, { type: 'separator' }, { label: 'Toggle Developer Tools', role: 'toggleDevTools' }, - { label: 'Open Calling Developer Tools', click: showCallingDevTools }, { label: 'Force Update', click: forceUpdate }, ], }); @@ -236,7 +234,6 @@ describe('createTemplate', () => { setupAsStandalone, showAbout, showDebugLog, - showCallingDevTools, showKeyboardShortcuts, showSettings, showWindow, diff --git a/ts/types/menu.std.ts b/ts/types/menu.std.ts index b436bab199..aa5cf142bf 100644 --- a/ts/types/menu.std.ts +++ b/ts/types/menu.std.ts @@ -26,7 +26,6 @@ export type MenuActionsType = Readonly<{ setupAsStandalone: () => unknown; showAbout: () => unknown; showDebugLog: () => unknown; - showCallingDevTools: () => unknown; showKeyboardShortcuts: () => unknown; showSettings: () => unknown; showWindow: () => unknown; diff --git a/ts/util/lint/license_comments.node.ts b/ts/util/lint/license_comments.node.ts index fe79baab2e..61c139faf0 100644 --- a/ts/util/lint/license_comments.node.ts +++ b/ts/util/lint/license_comments.node.ts @@ -50,22 +50,6 @@ const FILES_TO_IGNORE = new Set( 'js/WebAudioRecorderMp3.js', 'sticker-creator/src/util/protos.d.ts', 'sticker-creator/src/util/protos.js', - // ignore calling developer tools licensing which use Chromium license - 'calling_tools.html', - 'js/calling-tools/assert.js', - 'js/calling-tools/candidate_grid.js', - 'js/calling-tools/data_series.js', - 'js/calling-tools/dump_creator.js', - 'js/calling-tools/peer_connection_update_table.js', - 'js/calling-tools/stats_graph_helper.js', - 'js/calling-tools/stats_helper.js', - 'js/calling-tools/stats_rates_calculator.js', - 'js/calling-tools/stats_table.js', - 'js/calling-tools/tab_view.js', - 'js/calling-tools/timeline_graph_view.js', - 'js/calling-tools/user_media_table.js', - 'js/calling-tools/util.js', - 'js/calling-tools/webrtc_internals.dom.js', ].map( // This makes sure the files are correct on Windows. path.normalize diff --git a/ts/windows/calling-tools/preload.preload.ts b/ts/windows/calling-tools/preload.preload.ts deleted file mode 100644 index b5d3f669d6..0000000000 --- a/ts/windows/calling-tools/preload.preload.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import { contextBridge, ipcRenderer } from 'electron'; -import type { Event } from 'electron/renderer'; -import { MinimalSignalContext } from '../minimalContext.preload.js'; - -type RtcStatsReport = { - conversationId: string; - callId: string; - reportJson: string; -}; - -const Signal = { - CallingToolsProps: { - onRtcStatsReport: ( - callback: (event: Event, value: RtcStatsReport) => void - ) => ipcRenderer.on('calling:rtc-stats-report', callback), - setRtcStatsInterval: (intervalMillis: number) => { - ipcRenderer.send('calling:set-rtc-stats-interval', intervalMillis); - }, - }, -}; -contextBridge.exposeInMainWorld('Signal', Signal); -contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext); diff --git a/ts/windows/calling-tools/webrtc_internals.dom.ts b/ts/windows/calling-tools/webrtc_internals.dom.ts deleted file mode 100644 index 8a27fb3dc2..0000000000 --- a/ts/windows/calling-tools/webrtc_internals.dom.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2024 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import '../../../js/calling-tools/webrtc_internals.dom.js'; diff --git a/ts/windows/main/start.preload.ts b/ts/windows/main/start.preload.ts index f6a632eb06..fa2198ea67 100644 --- a/ts/windows/main/start.preload.ts +++ b/ts/windows/main/start.preload.ts @@ -120,8 +120,6 @@ if ( calling._iceServerOverride = override; }, - setRtcStatsInterval: (intervalMillis: number) => - calling.setAllRtcStatsInterval(intervalMillis), sendPollInSelectedConversation: async (poll: PollCreateType) => { if (!isPollSendEnabled()) { throw new Error('Poll sending is not enabled');