blob: acabc00407d6a55818d768157873282bca123669 [file] [log] [blame]
/**
* Copyright 2016 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* 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: 'number',
associateStatsId: 'string',
isRemote: 'boolean',
mediaType: 'string',
trackId: 'string',
transportId: 'string',
codecId: 'string',
firCount: 'number',
pliCount: 'number',
nackCount: 'number',
sliCount: 'number',
qpSum: 'number',
});
/*
* RTCCodecStats
* https://w3c.github.io/webrtc-stats/#codec-dict*
* @private
*/
var kRTCCodecStats = new RTCStats_(null, {
payloadType: 'number',
mimeType: 'string',
// TODO(hbos): As soon as |codec| has been renamed |mimeType| in the webrtc
// repo, remove this line. https://bugs.webrtc.org/7061
codec: 'string',
clockRate: 'number',
channels: 'number',
sdpFmtpLine: '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',
framesDecoded: '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',
framesEncoded: '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',
dataChannelsRequested: 'number',
dataChannelsAccepted: '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',
kind: 'string',
estimatedPlayoutTimestamp: 'number',
frameWidth: 'number',
frameHeight: 'number',
framesPerSecond: 'number',
framesCaptured: 'number',
framesSent: 'number',
framesReceived: 'number',
framesDecoded: 'number',
framesDropped: 'number',
framesCorrupted: 'number',
partialFramesLost: 'number',
fullFramesLost: 'number',
audioLevel: 'number',
totalAudioEnergy: 'number',
voiceActivityFlag: 'boolean',
echoReturnLoss: 'number',
echoReturnLossEnhancement: 'number',
totalSamplesSent: 'number',
totalSamplesReceived: 'number',
totalSamplesDuration: 'number',
concealedSamples: 'number',
concealmentEvents: 'number',
jitterBufferDelay: 'number',
priority: 'string'
});
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',
dtlsState: 'string',
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, {
transportId: 'string',
isRemote: 'boolean',
ip: 'string',
port: 'number',
protocol: 'string',
candidateType: 'string',
priority: 'number',
url: 'string',
deleted: 'boolean',
});
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',
totalRoundTripTime: 'number',
currentRoundTripTime: '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.
/**
* Verifies that the promise-based |RTCPeerConnection.getStats| returns stats,
* makes sure that all returned stats have the base RTCStats-members and that
* all stats are allowed by the whitelist.
*
* Returns "ok-" followed by JSON-stringified array of "RTCStats.type" values
* to the test, these being the different types of stats that was returned by
* this call to getStats.
*/
function verifyStatsGeneratedPromise() {
peerConnection_().getStats()
.then(function(report) {
if (report == null || report.size == 0)
throw new failTest('report is null or empty.');
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);
}
returnToTest('ok-' + JSON.stringify(Array.from(statsTypes.values())));
},
function(e) {
throw failTest('Promise was rejected: ' + e);
});
}
/**
* Gets the result of the promise-based |RTCPeerConnection.getStats| as a
* dictionary of RTCStats-dictionaries.
*
* Returns "ok-" followed by a JSON-stringified dictionary of dictionaries to
* the test.
*/
function getStatsReportDictionary() {
peerConnection_().getStats()
.then(function(report) {
if (report == null || report.size == 0)
throw new failTest('report is null or empty.');
let reportDictionary = {};
for (let stats of report.values()) {
reportDictionary[stats.id] = stats;
}
returnToTest('ok-' + JSON.stringify(reportDictionary));
},
function(e) {
throw failTest('Promise was rejected: ' + e);
});
}
/**
* Measures the performance of the promise-based |RTCPeerConnection.getStats|
* and returns the time it took in milliseconds as a double
* (DOMHighResTimeStamp, accurate to one thousandth of a millisecond).
* Verifies that all stats types of the whitelist were contained in the result.
*
* Returns "ok-" followed by a double on success.
*/
function measureGetStatsPerformance() {
let t0 = performance.now();
peerConnection_().getStats()
.then(function(report) {
let t1 = performance.now();
let statsTypes = new Set();
for (let stats of report.values()) {
verifyStatsIsWhitelisted_(stats);
statsTypes.add(stats.type);
}
if (statsTypes.size != gStatsWhitelist.size) {
returnToTest('The returned report contains a different number (' +
statsTypes.size + ') of stats types than the whitelist. ' +
JSON.stringify(Array.from(statsTypes)));
}
returnToTest('ok-' + (t1 - t0));
},
function(e) {
throw failTest('Promise was rejected: ' + e);
});
}
/**
* Returns a complete list of whitelisted "RTCStats.type" values as a
* JSON-stringified array of strings to the test.
*/
function getWhitelistedStatsTypes() {
returnToTest(JSON.stringify(Array.from(gStatsWhitelist.keys())));
}
// Internals.
/** @private */
function RTCStats_(parent, membersObject) {
if (parent != null)
Object.assign(this, parent);
Object.assign(this, membersObject);
}
/**
* 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] === 'any')
continue;
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]);
}
}
}
}
}