Chromium Code Reviews| Index: chrome/test/data/webrtc/peerconnection_getstats.js |
| diff --git a/chrome/test/data/webrtc/peerconnection_getstats.js b/chrome/test/data/webrtc/peerconnection_getstats.js |
| index 9258a3656f768a57bb8905d10787190ccaa162a8..73f9ed66469640b73131c1beaa234470c706f709 100644 |
| --- a/chrome/test/data/webrtc/peerconnection_getstats.js |
| +++ b/chrome/test/data/webrtc/peerconnection_getstats.js |
| @@ -4,6 +4,228 @@ |
| * found in the LICENSE file. |
| */ |
| +/** |
| + * Maps "RTCStats.type" values to descriptions of whitelisted (allowed to be |
| + * exposed to the web) RTCStats-derived dictionaries described below. |
| + * @private |
| + */ |
| +var gStatsWhitelist = new Map(); |
| + |
| +/** |
| + * RTCRTPStreamStats |
| + * https://w3c.github.io/webrtc-stats/#streamstats-dict* |
| + * @private |
| + */ |
| +var kRTCRTPStreamStats = new RTCStats_(null, { |
| + ssrc: 'string', |
| + associateStatsId: 'string', |
| + isRemote: 'boolean', |
| + mediaType: 'string', |
| + mediaTrackId: 'string', |
| + transportId: 'string', |
| + codecId: 'string', |
| + firCount: 'number', |
| + pliCount: 'number', |
| + nackCount: 'number', |
| + sliCount: 'number', |
| +}); |
| + |
| +/* |
| + * RTCCodecStats |
| + * https://w3c.github.io/webrtc-stats/#codec-dict* |
| + * @private |
| + */ |
| +var kRTCCodecStats = new RTCStats_(null, { |
| + payloadType: 'number', |
| + codec: 'string', |
| + clockRate: 'number', |
| + channels: 'number', |
| + parameters: 'string', |
| + implementation: 'string', |
| +}); |
| +gStatsWhitelist.set('codec', kRTCCodecStats); |
| + |
| +/* |
| + * RTCInboundRTPStreamStats |
| + * https://w3c.github.io/webrtc-stats/#inboundrtpstats-dict* |
| + * @private |
| + */ |
| +var kRTCInboundRTPStreamStats = new RTCStats_(kRTCRTPStreamStats, { |
| + packetsReceived: 'number', |
| + bytesReceived: 'number', |
| + packetsLost: 'number', |
| + jitter: 'number', |
| + fractionLost: 'number', |
| + packetsDiscarded: 'number', |
| + packetsRepaired: 'number', |
| + burstPacketsLost: 'number', |
| + burstPacketsDiscarded: 'number', |
| + burstLossCount: 'number', |
| + burstDiscardCount: 'number', |
| + burstLossRate: 'number', |
| + burstDiscardRate: 'number', |
| + gapLossRate: 'number', |
| + gapDiscardRate: 'number', |
| +}); |
| +gStatsWhitelist.set('inbound-rtp', kRTCInboundRTPStreamStats); |
| + |
| +/* |
| + * RTCOutboundRTPStreamStats |
| + * https://w3c.github.io/webrtc-stats/#outboundrtpstats-dict* |
| + * @private |
| + */ |
| +var kRTCOutboundRTPStreamStats = new RTCStats_(kRTCRTPStreamStats, { |
| + packetsSent: 'number', |
| + bytesSent: 'number', |
| + targetBitrate: 'number', |
| + roundTripTime: 'number', |
| +}); |
| +gStatsWhitelist.set('outbound-rtp', kRTCOutboundRTPStreamStats); |
| + |
| +/* |
| + * RTCPeerConnectionStats |
| + * https://w3c.github.io/webrtc-stats/#pcstats-dict* |
| + * @private |
| + */ |
| +var kRTCPeerConnectionStats = new RTCStats_(null, { |
| + dataChannelsOpened: 'number', |
| + dataChannelsClosed: 'number', |
| +}); |
| +gStatsWhitelist.set('peer-connection', kRTCPeerConnectionStats); |
| + |
| +/* |
| + * RTCMediaStreamStats |
| + * https://w3c.github.io/webrtc-stats/#msstats-dict* |
| + * @private |
| + */ |
| +var kRTCMediaStreamStats = new RTCStats_(null, { |
| + streamIdentifier: 'string', |
| + trackIds: 'sequence_string', |
| +}); |
| +gStatsWhitelist.set('stream', kRTCMediaStreamStats); |
| + |
| +/* |
| + * RTCMediaStreamTrackStats |
| + * https://w3c.github.io/webrtc-stats/#mststats-dict* |
| + * @private |
| + */ |
| +var kRTCMediaStreamTrackStats = new RTCStats_(null, { |
| + trackIdentifier: 'string', |
| + remoteSource: 'boolean', |
| + ended: 'boolean', |
| + detached: 'boolean', |
| + ssrcIds: 'sequence_string', |
| + frameWidth: 'number', |
| + frameHeight: 'number', |
| + framesPerSecond: 'number', |
| + framesSent: 'number', |
| + framesReceived: 'number', |
| + framesDecoded: 'number', |
| + framesDropped: 'number', |
| + framesCorrupted: 'number', |
| + partialFramesLost: 'number', |
| + fullFramesLost: 'number', |
| + audioLevel: 'number', |
| + echoReturnLoss: 'number', |
| + echoReturnLossEnhancement: 'number', |
| +}); |
| +gStatsWhitelist.set('track', kRTCMediaStreamTrackStats); |
| + |
| +/* |
| + * RTCDataChannelStats |
| + * https://w3c.github.io/webrtc-stats/#dcstats-dict* |
| + * @private |
| + */ |
| +var kRTCDataChannelStats = new RTCStats_(null, { |
| + label: 'string', |
| + protocol: 'string', |
| + datachannelid: 'number', |
| + state: 'string', |
| + messagesSent: 'number', |
| + bytesSent: 'number', |
| + messagesReceived: 'number', |
| + bytesReceived: 'number', |
| +}); |
| +gStatsWhitelist.set('data-channel', kRTCDataChannelStats); |
| + |
| +/* |
| + * RTCTransportStats |
| + * https://w3c.github.io/webrtc-stats/#transportstats-dict* |
| + * @private |
| + */ |
| +var kRTCTransportStats = new RTCStats_(null, { |
| + bytesSent: 'number', |
| + bytesReceived: 'number', |
| + rtcpTransportStatsId: 'string', |
| + activeConnection: 'boolean', |
| + selectedCandidatePairId: 'string', |
| + localCertificateId: 'string', |
| + remoteCertificateId: 'string', |
| +}); |
| +gStatsWhitelist.set('transport', kRTCTransportStats); |
| + |
| +/* |
| + * RTCIceCandidateStats |
| + * https://w3c.github.io/webrtc-stats/#icecandidate-dict* |
| + * @private |
| + */ |
| +var kRTCIceCandidateStats = new RTCStats_(null, { |
| + ip: 'string', |
| + port: 'number', |
| + protocol: 'string', |
| + candidateType: 'string', |
| + priority: 'number', |
| + url: 'string', |
| +}); |
| +gStatsWhitelist.set('local-candidate', kRTCIceCandidateStats); |
| +gStatsWhitelist.set('remote-candidate', kRTCIceCandidateStats); |
| + |
| +/* |
| + * RTCIceCandidatePairStats |
| + * https://w3c.github.io/webrtc-stats/#candidatepair-dict* |
| + * @private |
| + */ |
| +var kRTCIceCandidatePairStats = new RTCStats_(null, { |
| + transportId: 'string', |
| + localCandidateId: 'string', |
| + remoteCandidateId: 'string', |
| + state: 'string', |
| + priority: 'number', |
| + nominated: 'boolean', |
| + writable: 'boolean', |
| + readable: 'boolean', |
| + bytesSent: 'number', |
| + bytesReceived: 'number', |
| + totalRtt: 'number', |
| + currentRtt: 'number', |
| + availableOutgoingBitrate: 'number', |
| + availableIncomingBitrate: 'number', |
| + requestsReceived: 'number', |
| + requestsSent: 'number', |
| + responsesReceived: 'number', |
| + responsesSent: 'number', |
| + retransmissionsReceived: 'number', |
| + retransmissionsSent: 'number', |
| + consentRequestsReceived: 'number', |
| + consentRequestsSent: 'number', |
| + consentResponsesReceived: 'number', |
| + consentResponsesSent: 'number', |
| +}); |
| +gStatsWhitelist.set('candidate-pair', kRTCIceCandidatePairStats); |
| + |
| +/* |
| + * RTCCertificateStats |
| + * https://w3c.github.io/webrtc-stats/#certificatestats-dict* |
| + * @private |
| + */ |
| +var kRTCCertificateStats = new RTCStats_(null, { |
| + fingerprint: 'string', |
| + fingerprintAlgorithm: 'string', |
| + base64Certificate: 'string', |
| + issuerCertificateId: 'string', |
| +}); |
| +gStatsWhitelist.set('certificate', kRTCCertificateStats); |
| + |
| // Public interface to tests. These are expected to be called with |
| // ExecuteJavascript invocations from the browser tests and will return answers |
| // through the DOM automation controller. |
| @@ -11,36 +233,119 @@ |
| /** |
| * Verifies that the promise-based |RTCPeerConnection.getStats| returns stats. |
| * |
| - * Returns ok-got-stats on success. |
| + * Returns to test "ok-" followed by a list of "RTCStats.type" values as a |
| + * string of semicolon separated values, these being the different types of |
| + * stats that was returned by getStats. |
| */ |
| function verifyStatsGeneratedPromise() { |
| peerConnection_().getStats() |
| .then(function(report) { |
| if (report == null || report.size == 0) |
| throw new failTest('report is null or empty.'); |
| - // Sanity check that applies to all stats. |
| - var ids = new Set(); |
| - report.forEach(function(stats) { |
| - if (typeof(stats.id) !== 'string') |
| - throw failTest('stats.id is not a string.'); |
| + let statsTypes = new Set(); |
| + let ids = new Set(); |
| + for (let stats of report.values()) { |
| + verifyStatsIsWhitelisted_(stats); |
| + statsTypes.add(stats.type); |
| if (ids.has(stats.id)) |
| throw failTest('stats.id is not a unique identifier.'); |
| ids.add(stats.id); |
| - if (typeof(stats.timestamp) !== 'number' || stats.timestamp <= 0) |
| - throw failTest('stats.timestamp is not a positive number.'); |
| - if (typeof(stats.type) !== 'string') |
| - throw failTest('stats.type is not a string.'); |
| - }); |
| - // TODO(hbos): When the new stats collection API is more mature (and |
| - // certainly before unflagging the new stats API) add a whitelist of |
| - // allowed stats to prevent accidentally exposing stats to the web that |
| - // are not in the spec and that verifies type information. Status at |
| - // crbug.com/627816. Stats collection is tested in the WebRTC repo and |
| - // automatically surfaced to Blink, but there should be a process of |
| - // having to land a Blink CL in order to expose a new RTCStats dictionary. |
| - returnToTest('ok-got-stats'); |
| + } |
| + returnToTest('ok-' + iterableToSemicolonList(statsTypes.values())); |
|
phoglund_chromium
2016/11/09 15:50:10
I'd recommend using JSON.stringify here instead of
hbos_chromium
2016/11/09 17:24:32
Done. (The referenced JSON is being returned to C+
|
| }, |
| function(e) { |
| throw failTest('Promise was rejected: ' + e); |
| }); |
| } |
| + |
| +/** |
| + * Returns to test a complete list of whitelisted "RTCStats.type" values as a |
| + * string of semicolon separated values. |
| + */ |
| +function getWhitelistedStatsTypes() { |
| + returnToTest(iterableToSemicolonList(gStatsWhitelist.keys())); |
| +} |
| + |
| +// Internals. |
| + |
| +/** @private */ |
| +function RTCStats_(parent, membersObject) { |
| + if (parent != null) { |
| + for (let member in parent) { |
| + this[member] = parent[member]; |
| + } |
| + } |
| + for (let member in membersObject) { |
| + this[member] = membersObject[member]; |
| + } |
| +} |
| + |
| +/** |
| + * Checks if |stats| correctly maps to a a whitelisted RTCStats-derived |
| + * dictionary, throwing |failTest| if it doesn't. See |gStatsWhitelist|. |
| + * |
| + * The "RTCStats.type" must map to a known dictionary description. Every member |
| + * is optional, but if present it must be present in the whitelisted dictionary |
| + * description and its type must match. |
| + * @private |
| + */ |
| +function verifyStatsIsWhitelisted_(stats) { |
| + if (stats == null) |
| + throw failTest('stats is null or undefined: ' + stats); |
| + if (typeof(stats.id) !== 'string') |
| + throw failTest('stats.id is not a string:' + stats.id); |
| + if (typeof(stats.timestamp) !== 'number' || !isFinite(stats.timestamp) || |
| + stats.timestamp <= 0) { |
| + throw failTest('stats.timestamp is not a positive finite number: ' + |
| + stats.timestamp); |
| + } |
| + if (typeof(stats.type) !== 'string') |
| + throw failTest('stats.type is not a string: ' + stats.type); |
| + let whitelistedStats = gStatsWhitelist.get(stats.type); |
| + if (whitelistedStats == null) |
| + throw failTest('stats.type is not a whitelisted type: ' + stats.type); |
| + for (let propertyName in stats) { |
| + if (propertyName === 'id' || propertyName === 'timestamp' || |
| + propertyName === 'type') { |
| + continue; |
| + } |
| + if (!whitelistedStats.hasOwnProperty(propertyName)) { |
| + throw failTest('stats.' + propertyName + ' is not a whitelisted ' + |
| + 'member: ' + stats[propertyName]); |
| + } |
| + if (!whitelistedStats[propertyName].startsWith('sequence_')) { |
| + if (typeof(stats[propertyName]) !== whitelistedStats[propertyName]) { |
| + throw failTest('stats.' + propertyName + ' should have a different ' + |
| + 'type according to the whitelist: ' + stats[propertyName] + ' vs ' + |
| + whitelistedStats[propertyName]); |
| + } |
| + } else { |
| + if (!Array.isArray(stats[propertyName])) { |
| + throw failTest('stats.' + propertyName + ' should have a different ' + |
| + 'type according to the whitelist (should be an array): ' + |
| + JSON.stringify(stats[propertyName]) + ' vs ' + |
| + whitelistedStats[propertyName]); |
| + } |
| + let elementType = whitelistedStats[propertyName].substring(9); |
| + for (let element in stats[propertyName]) { |
| + if (typeof(element) !== elementType) { |
| + throw failTest('stats.' + propertyName + ' should have a different ' + |
| + 'type according to the whitelist (an element of the array has ' + |
| + 'the incorrect type): ' + JSON.stringify(stats[propertyName]) + |
| + ' vs ' + whitelistedStats[propertyName]); |
| + } |
| + } |
| + } |
| + } |
| +} |
| + |
| +/** @private */ |
| +function iterableToSemicolonList(iterable) { |
| + let str = ''; |
| + for (let element of iterable) { |
| + if (str.length > 0) |
| + str += ';'; |
| + str += element; |
| + } |
| + return str; |
| +} |