diff --git a/content/media/webrtc/PeerIdentity.cpp b/content/media/webrtc/PeerIdentity.cpp index e53c7d546e2..79a1669e000 100644 --- a/content/media/webrtc/PeerIdentity.cpp +++ b/content/media/webrtc/PeerIdentity.cpp @@ -9,6 +9,7 @@ #include "nsCOMPtr.h" #include "nsIIDNService.h" #include "nsNetCID.h" +#include "nsServiceManagerUtils.h" namespace mozilla { @@ -36,7 +37,7 @@ PeerIdentity::Equals(const nsAString& aOtherString) const nsresult rv; nsCOMPtr idnService - = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv); + = do_GetService("@mozilla.org/network/idn-service;1", &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return host == otherHost; } diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index 3b416ff731c..349297891f9 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -24,6 +24,7 @@ #include "nsISupportsPrimitives.h" #include "nsIInterfaceRequestorUtils.h" #include "mozilla/Types.h" +#include "mozilla/PeerIdentity.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/MediaStreamBinding.h" #include "mozilla/dom/MediaStreamTrackBinding.h" @@ -39,6 +40,8 @@ #include "nsDOMFile.h" #include "nsGlobalWindow.h" +#include "mozilla/Preferences.h" + /* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */ #include "MediaEngineDefault.h" #if defined(MOZ_WEBRTC) @@ -494,11 +497,13 @@ public: uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener* aListener, MediaEngineSource* aAudioSource, - MediaEngineSource* aVideoSource) + MediaEngineSource* aVideoSource, + PeerIdentity* aPeerIdentity) : mAudioSource(aAudioSource) , mVideoSource(aVideoSource) , mWindowID(aWindowID) , mListener(aListener) + , mPeerIdentity(aPeerIdentity) , mManager(MediaManager::GetInstance()) { mSuccess.swap(aSuccess); @@ -621,9 +626,16 @@ public: reinterpret_cast(stream.get()), reinterpret_cast(trackunion->GetStream())); - trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal()); + nsCOMPtr principal; + if (mPeerIdentity) { + principal = do_CreateInstance("@mozilla.org/nullprincipal;1"); + trackunion->SetPeerIdentity(mPeerIdentity.forget()); + } else { + principal = window->GetExtantDoc()->NodePrincipal(); + } + trackunion->CombineWithPrincipal(principal); - // The listener was added at the begining in an inactive state. + // The listener was added at the beginning in an inactive state. // Activate our listener. We'll call Start() on the source when get a callback // that the MediaStream has started consuming. The listener is freed // when the page is invalidated (on navigation or close). @@ -662,6 +674,7 @@ private: nsRefPtr mVideoSource; uint64_t mWindowID; nsRefPtr mListener; + nsAutoPtr mPeerIdentity; nsRefPtr mManager; // get ref to this when creating the runnable }; @@ -1055,9 +1068,14 @@ public: return; } } + PeerIdentity* peerIdentity = nullptr; + if (!mConstraints.mPeerIdentity.IsEmpty()) { + peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity); + } NS_DispatchToMainThread(new GetUserMediaStreamRunnable( - mSuccess, mError, mWindowID, mListener, aAudioSource, aVideoSource + mSuccess, mError, mWindowID, mListener, aAudioSource, aVideoSource, + peerIdentity )); MOZ_ASSERT(!mSuccess); @@ -1296,8 +1314,8 @@ MediaManager::NotifyRecordingStatusChange(nsPIDOMWindow* aWindow, props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL); obs->NotifyObservers(static_cast(props), - "recording-device-events", - aMsg.get()); + "recording-device-events", + aMsg.get()); // Forward recording events to parent process. // The events are gathered in chrome process and used for recording indicator @@ -1676,8 +1694,7 @@ MediaManager::RemoveFromWindowList(uint64_t aWindowID, // Notify the UI that this window no longer has gUM active char windowBuffer[32]; PR_snprintf(windowBuffer, sizeof(windowBuffer), "%llu", outerID); - nsAutoString data; - data.Append(NS_ConvertUTF8toUTF16(windowBuffer)); + nsString data = NS_ConvertUTF8toUTF16(windowBuffer); nsCOMPtr obs = services::GetObserverService(); obs->NotifyObservers(nullptr, "recording-window-ended", data.get()); diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js index bab31bc613f..30f210d28e4 100644 --- a/dom/media/PeerConnection.js +++ b/dom/media/PeerConnection.js @@ -291,8 +291,8 @@ RTCPeerConnection.prototype = { this._trickleIce = Services.prefs.getBoolPref("media.peerconnection.trickle_ice"); if (!rtcConfig.iceServers || !Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) { - rtcConfig = {iceServers: - JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers"))}; + rtcConfig.iceServers = + JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers")); } this._mustValidateRTCConfiguration(rtcConfig, "RTCPeerConnection constructor passed invalid RTCConfiguration"); @@ -347,6 +347,19 @@ RTCPeerConnection.prototype = { return this._pc; }, + callCB: function(callback, arg) { + if (callback) { + try { + callback(arg); + } catch(e) { + // A content script (user-provided) callback threw an error. We don't + // want this to take down peerconnection, but we still want the user + // to see it, so we catch it, report it, and move on. + this.logErrorAndCallOnError(e.message, e.fileName, e.lineNumber); + } + } + }, + _initIdp: function() { let prefName = "media.peerconnection.identity.timeout"; let idpTimeout = Services.prefs.getIntPref(prefName); @@ -656,15 +669,6 @@ RTCPeerConnection.prototype = { "Invalid type " + desc.type + " provided to setRemoteDescription"); } - try { - let processIdentity = this._processIdentity.bind(this); - this._remoteIdp.verifyIdentityFromSDP(desc.sdp, processIdentity); - } catch (e) { - this.logWarning(e.message, e.fileName, e.lineNumber); - // only happens if processing the SDP for identity doesn't work - // let _setRemoteDescription do the error reporting - } - this._queueOrRun({ func: this._setRemoteDescription, args: [type, desc.sdp, onSuccess, onError], @@ -673,18 +677,84 @@ RTCPeerConnection.prototype = { }); }, - _processIdentity: function(message) { - if (message) { + /** + * Takes a result from the IdP and checks it against expectations. + * If OK, generates events. + * Returns true if it is either present and valid, or if there is no + * need for identity. + */ + _processIdpResult: function(message) { + let good = !!message; + // This might be a valid assertion, but if we are constrained to a single peer + // identity, then we also need to make sure that the assertion matches + if (good && this._impl.peerIdentity) { + good = (message.identity === this._impl.peerIdentity); + } + if (good) { + this._impl.peerIdentity = message.identity; this._peerIdentity = new this._win.RTCIdentityAssertion( - this._remoteIdp.provider, message.identity); - - let args = { peerIdentity: this._peerIdentity }; + this._remoteIdp.provider, message.identity); this.dispatchEvent(new this._win.Event("peeridentity")); } + return good; }, _setRemoteDescription: function(type, sdp, onSuccess, onError) { - this._onSetRemoteDescriptionSuccess = onSuccess; + let idpComplete = false; + let setRemoteComplete = false; + let idpError = null; + + // we can run the IdP validation in parallel with setRemoteDescription this + // complicates much more than would be ideal, but it ensures that the IdP + // doesn't hold things up too much when it's not on the critical path + let allDone = () => { + if (!setRemoteComplete || !idpComplete || !onSuccess) { + return; + } + this._remoteType = this._pendingType; + this._pendingType = null; + this.callCB(onSuccess); + onSuccess = null; + this._executeNext(); + }; + + let setRemoteDone = () => { + setRemoteComplete = true; + allDone(); + }; + + // If we aren't waiting for something specific, allow this + // to complete asynchronously. + let idpDone; + if (!this._impl.peerIdentity) { + idpDone = this._processIdpResult.bind(this); + idpComplete = true; // lie about this for allDone() + } else { + idpDone = message => { + let idpGood = this._processIdpResult(message); + if (!idpGood) { + // iff we are waiting for a very specific peerIdentity + // call the error callback directly and then close + idpError = "Peer Identity mismatch, expected: " + + this._impl.peerIdentity; + this.callCB(onError, idpError); + this.close(); + } else { + idpComplete = true; + allDone(); + } + }; + } + + try { + this._remoteIdp.verifyIdentityFromSDP(sdp, idpDone); + } catch (e) { + // if processing the SDP for identity doesn't work + this.logWarning(e.message, e.fileName, e.lineNumber); + idpDone(null); + } + + this._onSetRemoteDescriptionSuccess = setRemoteDone; this._onSetRemoteDescriptionFailure = onError; this._impl.setRemoteDescription(type, sdp); }, @@ -703,14 +773,14 @@ RTCPeerConnection.prototype = { getIdentityAssertion: function() { this._checkClosed(); - function gotAssertion(assertion) { + var gotAssertion = assertion => { if (assertion) { this._gotIdentityAssertion(assertion); } - } + }; this._localIdp.getIdentityAssertion(this._impl.fingerprint, - gotAssertion.bind(this)); + gotAssertion); }, updateIce: function(config, constraints) { @@ -964,21 +1034,6 @@ PeerConnectionObserver.prototype = { this._dompc.dispatchEvent(event); }, - callCB: function(callback, arg) { - if (callback) { - try { - callback(arg); - } catch(e) { - // A content script (user-provided) callback threw an error. We don't - // want this to take down peerconnection, but we still want the user - // to see it, so we catch it, report it, and move on. - this._dompc.logErrorAndCallOnError(e.message, - e.fileName, - e.lineNumber); - } - } - }, - onCreateOfferSuccess: function(sdp) { let pc = this._dompc; let fp = pc._impl.fingerprint; @@ -986,15 +1041,15 @@ PeerConnectionObserver.prototype = { if (assertion) { pc._gotIdentityAssertion(assertion); } - this.callCB(pc._onCreateOfferSuccess, - new pc._win.mozRTCSessionDescription({ type: "offer", - sdp: sdp })); + pc.callCB(pc._onCreateOfferSuccess, + new pc._win.mozRTCSessionDescription({ type: "offer", + sdp: sdp })); pc._executeNext(); }.bind(this)); }, onCreateOfferError: function(code, message) { - this.callCB(this._dompc._onCreateOfferFailure, new RTCError(code, message)); + this._dompc.callCB(this._dompc._onCreateOfferFailure, new RTCError(code, message)); this._dompc._executeNext(); }, @@ -1005,22 +1060,23 @@ PeerConnectionObserver.prototype = { if (assertion) { pc._gotIdentityAssertion(assertion); } - this.callCB (pc._onCreateAnswerSuccess, - new pc._win.mozRTCSessionDescription({ type: "answer", - sdp: sdp })); + pc.callCB(pc._onCreateAnswerSuccess, + new pc._win.mozRTCSessionDescription({ type: "answer", + sdp: sdp })); pc._executeNext(); }.bind(this)); }, onCreateAnswerError: function(code, message) { - this.callCB(this._dompc._onCreateAnswerFailure, new RTCError(code, message)); + this._dompc.callCB(this._dompc._onCreateAnswerFailure, + new RTCError(code, message)); this._dompc._executeNext(); }, onSetLocalDescriptionSuccess: function() { this._dompc._localType = this._dompc._pendingType; this._dompc._pendingType = null; - this.callCB(this._dompc._onSetLocalDescriptionSuccess); + this._dompc.callCB(this._dompc._onSetLocalDescriptionSuccess); if (this._dompc._iceGatheringState == "complete") { // If we are not trickling or we completed gathering prior @@ -1032,35 +1088,33 @@ PeerConnectionObserver.prototype = { }, onSetRemoteDescriptionSuccess: function() { - this._dompc._remoteType = this._dompc._pendingType; - this._dompc._pendingType = null; - this.callCB(this._dompc._onSetRemoteDescriptionSuccess); - this._dompc._executeNext(); + this._dompc._onSetRemoteDescriptionSuccess(); }, onSetLocalDescriptionError: function(code, message) { this._dompc._pendingType = null; - this.callCB(this._dompc._onSetLocalDescriptionFailure, - new RTCError(code, message)); + this._dompc.callCB(this._dompc._onSetLocalDescriptionFailure, + new RTCError(code, message)); this._dompc._executeNext(); }, onSetRemoteDescriptionError: function(code, message) { this._dompc._pendingType = null; - this.callCB(this._dompc._onSetRemoteDescriptionFailure, - new RTCError(code, message)); + this._dompc.callCB(this._dompc._onSetRemoteDescriptionFailure, + new RTCError(code, message)); this._dompc._executeNext(); }, onAddIceCandidateSuccess: function() { this._dompc._pendingType = null; - this.callCB(this._dompc._onAddIceCandidateSuccess); + this._dompc.callCB(this._dompc._onAddIceCandidateSuccess); this._dompc._executeNext(); }, onAddIceCandidateError: function(code, message) { this._dompc._pendingType = null; - this.callCB(this._dompc._onAddIceCandidateError, new RTCError(code, message)); + this._dompc.callCB(this._dompc._onAddIceCandidateError, + new RTCError(code, message)); this._dompc._executeNext(); }, @@ -1161,8 +1215,8 @@ PeerConnectionObserver.prototype = { onStateChange: function(state) { switch (state) { case "SignalingState": - this.callCB(this._dompc.onsignalingstatechange, - this._dompc.signalingState); + this._dompc.callCB(this._dompc.onsignalingstatechange, + this._dompc.signalingState); break; case "IceConnectionState": @@ -1196,18 +1250,20 @@ PeerConnectionObserver.prototype = { let webidlobj = this._dompc._win.RTCStatsReport._create(this._dompc._win, chromeobj); chromeobj.makeStatsPublic(); - this.callCB(this._dompc._onGetStatsSuccess, webidlobj); + this._dompc.callCB(this._dompc._onGetStatsSuccess, webidlobj); this._dompc._executeNext(); }, onGetStatsError: function(code, message) { - this.callCB(this._dompc._onGetStatsFailure, new RTCError(code, message)); + this._dompc.callCB(this._dompc._onGetStatsFailure, + new RTCError(code, message)); this._dompc._executeNext(); }, onAddStream: function(stream) { - this.dispatchEvent(new this._dompc._win.MediaStreamEvent("addstream", - { stream: stream })); + let ev = new this._dompc._win.MediaStreamEvent("addstream", + { stream: stream }); + this._dompc.dispatchEvent(ev); }, onRemoveStream: function(stream, type) { diff --git a/dom/media/moz.build b/dom/media/moz.build index 66da2d464b7..0f2a479aba3 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -60,6 +60,7 @@ FAIL_ON_WARNINGS = True LOCAL_INCLUDES += [ '../base', '../camera', + '/caps/include', ] include('/ipc/chromium/chromium-config.mozbuild') diff --git a/dom/media/tests/identity/mochitest.ini b/dom/media/tests/identity/mochitest.ini index 721e50dae98..199669824a5 100644 --- a/dom/media/tests/identity/mochitest.ini +++ b/dom/media/tests/identity/mochitest.ini @@ -5,8 +5,8 @@ support-files = /.well-known/idp-proxy/idp-proxy.js identityevent.js -# All tests are disabled on android due to lack of https support in mochitest -# (Bug 975149) +# All tests are disabled on android&b2g due to lack of https support in +# mochitests (Bug 907770) # All tests are disabled on b2g due to lack of e10s support in WebRTC identity # (Bug 975144) [test_idpproxy.html] @@ -17,5 +17,7 @@ skip-if = os == "android" || appname == "b2g" skip-if = os == "android" || appname == "b2g" [test_setIdentityProviderWithErrors.html] skip-if = os == "android" || appname == "b2g" +[test_peerConnection_peerIdentity.html] +skip-if = os == "android" || appname == "b2g" [../mochitest/test_zmedia_cleanup.html] skip-if = os == "android" || appname == "b2g" diff --git a/dom/media/tests/identity/test_getIdentityAssertion.html b/dom/media/tests/identity/test_getIdentityAssertion.html index cbf60bce49a..9d8e61ecf75 100644 --- a/dom/media/tests/identity/test_getIdentityAssertion.html +++ b/dom/media/tests/identity/test_getIdentityAssertion.html @@ -26,6 +26,7 @@ var test; function theTest() { test = new PeerConnectionTest(); test.setMediaConstraints([{audio: true}], [{audio: true}]); + test.chain.removeAfter('PC_REMOTE_CHECK_INITIAL_SIGNALINGSTATE'); test.chain.append([ [ "GET_IDENTITY_ASSERTION_FAILS_WITHOUT_PROVIDER", diff --git a/dom/media/tests/identity/test_peerConnection_peerIdentity.html b/dom/media/tests/identity/test_peerConnection_peerIdentity.html new file mode 100644 index 00000000000..a35f619b327 --- /dev/null +++ b/dom/media/tests/identity/test_peerConnection_peerIdentity.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + +
+
+
+
+ + diff --git a/dom/media/tests/mochitest/blacksilence.js b/dom/media/tests/mochitest/blacksilence.js new file mode 100644 index 00000000000..8a0c769207d --- /dev/null +++ b/dom/media/tests/mochitest/blacksilence.js @@ -0,0 +1,114 @@ +(function(global) { + 'use strict'; + + // an invertible check on the condition. + // if the constraint is applied, then the check is direct + // if not applied, then the result should be reversed + function check(constraintApplied, condition, message) { + var good = constraintApplied ? condition : !condition; + message = (constraintApplied ? 'with' : 'without') + + ' constraint: should ' + (constraintApplied ? '' : 'not ') + + message + ' = ' + (good ? 'OK' : 'waiting...'); + info(message); + return good; + } + + function isSilence(audioData) { + var silence = true; + for (var i = 0; i < audioData.length; ++i) { + if (audioData[i] !== 128) { + silence = false; + } + } + return silence; + } + + function periodicCheck(type, checkFunc, successMessage, done) { + var interval = setInterval(function periodic() { + if (checkFunc()) { + ok(true, type + ' is ' + successMessage); + clearInterval(interval); + interval = null; + done(); + } + }, 200); + return function cancel() { + if (interval) { + ok(false, 'timed out waiting for audio check'); + clearInterval(interval); + done(); + } + }; + } + + function checkAudio(constraintApplied, stream, done) { + var context = new AudioContext(); + var source = context.createMediaStreamSource(stream); + var analyser = context.createAnalyser(); + source.connect(analyser); + analyser.connect(context.destination); + + function testAudio() { + var sampleCount = analyser.frequencyBinCount; + info('got some audio samples: ' + sampleCount); + var bucket = new ArrayBuffer(sampleCount); + var view = new Uint8Array(bucket); + analyser.getByteTimeDomainData(view); + + var silent = check(constraintApplied, isSilence(view), 'be silence for audio'); + // TODO: silence cross origin input to webaudio, bug 966066 + silent = constraintApplied ? !silent : silent; + return sampleCount > 0 && silent; + } + return periodicCheck('audio', testAudio, + (constraintApplied ? '' : 'not ') + 'silent', done); + } + + function mkElement(type) { + var display = document.getElementById('display'); + var e = document.createElement(type); + e.width = 32; + e.height = 24; + display.appendChild(e); + return e; + } + + function checkVideo(constraintApplied, stream, done) { + var video = mkElement('video'); + video.mozSrcObject = stream; + + var ready = false; + video.onplaying = function() { + ready = true; + } + video.play(); + + function tryToRenderToCanvas() { + if (!ready) { + info('waiting for video to start'); + return false; + } + + try { + // every try needs a new canvas, otherwise a taint from an earlier call + // will affect subsequent calls + var canvas = mkElement('canvas'); + var ctx = canvas.getContext('2d'); + // have to guard drawImage with the try as well, due to bug 879717 + // if we get an error, this round fails, but that failure is usually + // just transitory + ctx.drawImage(video, 0, 0); + ctx.getImageData(0, 0, 1, 1); + return check(constraintApplied, false, 'throw on getImageData for video'); + } catch (e) { + return check(constraintApplied, e.name === 'SecurityError', 'get a security error'); + } + } + + return periodicCheck('video', tryToRenderToCanvas, + (constraintApplied ? '' : 'not ') + 'protected', done); + } + + global.audioIsSilence = checkAudio; + global.videoIsBlack = checkVideo; +}(this)); diff --git a/dom/media/tests/mochitest/mochitest.ini b/dom/media/tests/mochitest/mochitest.ini index bd12cd81817..dcaf6a22a7e 100644 --- a/dom/media/tests/mochitest/mochitest.ini +++ b/dom/media/tests/mochitest/mochitest.ini @@ -6,6 +6,7 @@ support-files = pc.js templates.js NetworkPreparationChromeScript.js + blacksilence.js [test_dataChannel_basicAudio.html] skip-if = toolkit == 'gonk' #Bug 962984 for debug, bug 963244 for opt @@ -42,6 +43,7 @@ skip-if = (toolkit == 'gonk' && debug) #debug-only failure [test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html] [test_getUserMedia_stopVideoStream.html] [test_getUserMedia_stopVideoStreamWithFollowupVideo.html] +[test_getUserMedia_peerIdentity.html] [test_peerConnection_addCandidateInHaveLocalOffer.html] [test_peerConnection_basicAudio.html] skip-if = (toolkit == 'gonk' && debug) #Bug 962984, test fail on b2g debug build diff --git a/dom/media/tests/mochitest/test_getUserMedia_peerIdentity.html b/dom/media/tests/mochitest/test_getUserMedia_peerIdentity.html new file mode 100644 index 00000000000..6a4ae702bb1 --- /dev/null +++ b/dom/media/tests/mochitest/test_getUserMedia_peerIdentity.html @@ -0,0 +1,59 @@ + + + + + + Test mozGetUserMedia peerIdentity Constraint + + + + + + +Test mozGetUserMedia peerIdentity Constraint +

+ +
+
+
+ + diff --git a/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp b/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp index a0a8116e90d..37fb077f3fc 100644 --- a/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp +++ b/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp @@ -30,6 +30,10 @@ #include "nsServiceManagerUtils.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" +#ifdef MOZILLA_INTERNAL_API +#include "nsIPrincipal.h" +#include "nsIDocument.h" +#endif #include #include @@ -173,40 +177,40 @@ void VcmSIPCCBinding::CandidateReady(NrIceMediaStream* stream, char *candidate_tmp = (char *)malloc(candidate.size() + 1); if (!candidate_tmp) - return; + return; sstrncpy(candidate_tmp, candidate.c_str(), candidate.size() + 1); // Send a message to the GSM thread. CC_CallFeature_FoundICECandidate(vcm_opaque->call_handle_, - candidate_tmp, - nullptr, - vcm_opaque->level_, - nullptr); + candidate_tmp, + nullptr, + vcm_opaque->level_, + nullptr); } void VcmSIPCCBinding::setStreamObserver(StreamObserver* obs) { - streamObserver = obs; + streamObserver = obs; } /* static */ StreamObserver * VcmSIPCCBinding::getStreamObserver() { if (gSelf != nullptr) - return gSelf->streamObserver; + return gSelf->streamObserver; return nullptr; } void VcmSIPCCBinding::setMediaProviderObserver(MediaProviderObserver* obs) { - mediaProviderObserver = obs; + mediaProviderObserver = obs; } MediaProviderObserver * VcmSIPCCBinding::getMediaProviderObserver() { if (gSelf != nullptr) - return gSelf->mediaProviderObserver; + return gSelf->mediaProviderObserver; return nullptr; } @@ -571,7 +575,7 @@ static short vcmGetIceStream_m(cc_mcapid_t mcap_id, * */ static short vcmRxAllocICE_s(TemporaryRef ctx_in, - TemporaryRef stream_in, + TemporaryRef stream_in, cc_call_handle_t call_handle, cc_streamid_t stream_id, uint16_t level, @@ -1340,7 +1344,7 @@ short vcmRxOpen(cc_mcapid_t mcap_id, *port_allocated = -1; if(listen_ip) { - csf_sprintf(dottedIP, sizeof(dottedIP), "%u.%u.%u.%u", + csf_sprintf(dottedIP, sizeof(dottedIP), "%u.%u.%u.%u", (listen_ip->u.ip4 >> 24) & 0xff, (listen_ip->u.ip4 >> 16) & 0xff, (listen_ip->u.ip4 >> 8) & 0xff, listen_ip->u.ip4 & 0xff ); } @@ -1651,7 +1655,7 @@ static int vcmRxStartICE_m(cc_mcapid_t mcap_id, return VCM_ERROR; // Now we have all the pieces, create the pipeline - mozilla::RefPtr pipeline = + mozilla::RefPtr pipeline = new mozilla::MediaPipelineReceiveAudio( pc.impl()->GetHandle(), pc.impl()->GetMainThread().get(), @@ -1712,7 +1716,7 @@ static int vcmRxStartICE_m(cc_mcapid_t mcap_id, return VCM_ERROR; // Now we have all the pieces, create the pipeline - mozilla::RefPtr pipeline = + mozilla::RefPtr pipeline = new mozilla::MediaPipelineReceiveVideo( pc.impl()->GetHandle(), pc.impl()->GetMainThread().get(), @@ -1887,7 +1891,7 @@ void vcmRxReleasePort (cc_mcapid_t mcap_id, StreamObserver* obs = VcmSIPCCBinding::getStreamObserver(); if(obs != nullptr) - obs->deregisterStream(call_handle, stream_id); + obs->deregisterStream(call_handle, stream_id); } /* @@ -2272,6 +2276,103 @@ int vcmTxStart(cc_mcapid_t mcap_id, return VCM_ERROR; } +/** + * Create a conduit for audio transmission. + * + * @param[in] level - the m-line index + * @param[in] payload - codec info + * @param[in] pc - the peer connection + * @param [in] attrs - additional audio attributes + * @param[out] conduit - the conduit to create + */ +static int vcmTxCreateAudioConduit(int level, + const vcm_payload_info_t *payload, + sipcc::PeerConnectionWrapper &pc, + const vcm_mediaAttrs_t *attrs, + mozilla::RefPtr &conduit) +{ + mozilla::AudioCodecConfig *config_raw = + new mozilla::AudioCodecConfig( + payload->remote_rtp_pt, + ccsdpCodecName(payload->codec_type), + payload->audio.frequency, + payload->audio.packet_size, + payload->audio.channels, + payload->audio.bitrate, + pc.impl()->load_manager()); + + // Take possession of this pointer + mozilla::ScopedDeletePtr config(config_raw); + + // Instantiate an appropriate conduit + mozilla::RefPtr rx_conduit = + pc.impl()->media()->GetConduit(level, true); + MOZ_ASSERT_IF(rx_conduit, rx_conduit->type() == MediaSessionConduit::AUDIO); + + // The two sides of a send/receive pair of conduits each keep a raw pointer to the other, + // and are responsible for cleanly shutting down. + mozilla::RefPtr tx_conduit = + mozilla::AudioSessionConduit::Create( + static_cast(rx_conduit.get())); + + if (!tx_conduit || tx_conduit->ConfigureSendMediaCodec(config) || + tx_conduit->EnableAudioLevelExtension(attrs->audio_level, + attrs->audio_level_id)) { + return VCM_ERROR; + } + CSFLogError(logTag, "Created audio pipeline audio level %d %d", + attrs->audio_level, attrs->audio_level_id); + + conduit = tx_conduit; + return 0; +} + +/** + * Create a conduit for video transmission. + * + * @param[in] level - the m-line index + * @param[in] payload - codec info + * @param[in] pc - the peer connection + * @param[out] conduit - the conduit to create + */ +static int vcmTxCreateVideoConduit(int level, + const vcm_payload_info_t *payload, + sipcc::PeerConnectionWrapper &pc, + mozilla::RefPtr &conduit) +{ + mozilla::VideoCodecConfig *config_raw; + config_raw = new mozilla::VideoCodecConfig( + payload->remote_rtp_pt, + ccsdpCodecName(payload->codec_type), + payload->video.rtcp_fb_types, + payload->video.max_fs, + payload->video.max_fr, + pc.impl()->load_manager()); + + // Take possession of this pointer + mozilla::ScopedDeletePtr config(config_raw); + + // Instantiate an appropriate conduit + mozilla::RefPtr rx_conduit = + pc.impl()->media()->GetConduit(level, true); + MOZ_ASSERT_IF(rx_conduit, rx_conduit->type() == MediaSessionConduit::VIDEO); + + // The two sides of a send/receive pair of conduits each keep a raw pointer to the other, + // and are responsible for cleanly shutting down. + mozilla::RefPtr tx_conduit = + mozilla::VideoSessionConduit::Create(static_cast(rx_conduit.get())); + if (!tx_conduit) { + return VCM_ERROR; + } + if (vcmEnsureExternalCodec(tx_conduit, config, true)) { + return VCM_ERROR; + } + if (tx_conduit->ConfigureSendMediaCodec(config)) { + return VCM_ERROR; + } + conduit = tx_conduit; + return 0; +} /** * start tx stream @@ -2282,7 +2383,7 @@ int vcmTxStart(cc_mcapid_t mcap_id, * @param[in] stream_id - stream id of the given media type. * @param[in] level - the m-line index * @param[in] pc_stream_id - the media stream index (from PC.addStream()) - * @param[i]n pc_track_id - the track within the media stream + * @param[in] pc_track_id - the track within the media stream * @param[in] call_handle - call handle * @param[in] peerconnection - the peerconnection in use * @param[in] payload - payload information @@ -2298,21 +2399,21 @@ int vcmTxStart(cc_mcapid_t mcap_id, #define EXTRACT_DYNAMIC_PAYLOAD_TYPE(PTYPE) ((PTYPE)>>16) static int vcmTxStartICE_m(cc_mcapid_t mcap_id, - cc_groupid_t group_id, - cc_streamid_t stream_id, - int level, - int pc_stream_id, - int pc_track_id, - cc_call_handle_t call_handle, - const char *peerconnection, - const vcm_payload_info_t *payload, - short tos, - sdp_setup_type_e setup_type, - const char *fingerprint_alg, - const char *fingerprint, - vcm_mediaAttrs_t *attrs) + cc_groupid_t group_id, + cc_streamid_t stream_id, + int level, + int pc_stream_id, + int pc_track_id, + cc_call_handle_t call_handle, + const char *peerconnection, + const vcm_payload_info_t *payload, + short tos, + sdp_setup_type_e setup_type, + const char *fingerprint_alg, + const char *fingerprint, + vcm_mediaAttrs_t *attrs) { - CSFLogDebug( logTag, "%s(%s) track = %d, stream = %d, level = %d", + CSFLogDebug(logTag, "%s(%s) track = %d, stream = %d, level = %d", __FUNCTION__, peerconnection, pc_track_id, @@ -2322,19 +2423,20 @@ static int vcmTxStartICE_m(cc_mcapid_t mcap_id, // Find the PC and get the stream sipcc::PeerConnectionWrapper pc(peerconnection); ENSURE_PC(pc, VCM_ERROR); - nsRefPtr stream = pc.impl()->media()-> - GetLocalStream(pc_stream_id); + nsRefPtr stream = + pc.impl()->media()->GetLocalStream(pc_stream_id); // Create the transport flows mozilla::RefPtr rtp_flow = - vcmCreateTransportFlow(pc.impl(), level, false, setup_type, - fingerprint_alg, fingerprint); + vcmCreateTransportFlow(pc.impl(), level, false, setup_type, + fingerprint_alg, fingerprint); if (!rtp_flow) { - CSFLogError( logTag, "Could not create RTP flow"); - return VCM_ERROR; + CSFLogError(logTag, "Could not create RTP flow"); + return VCM_ERROR; } + mozilla::RefPtr rtcp_flow = nullptr; - if(!attrs->rtcp_mux) { + if (!attrs->rtcp_mux) { rtcp_flow = vcmCreateTransportFlow(pc.impl(), level, true, setup_type, fingerprint_alg, fingerprint); if (!rtcp_flow) { @@ -2343,121 +2445,55 @@ static int vcmTxStartICE_m(cc_mcapid_t mcap_id, } } - + const char *mediaType; + mozilla::RefPtr conduit; + int err = VCM_ERROR; if (CC_IS_AUDIO(mcap_id)) { - mozilla::AudioCodecConfig *config_raw; - config_raw = new mozilla::AudioCodecConfig( - payload->remote_rtp_pt, - ccsdpCodecName(payload->codec_type), - payload->audio.frequency, - payload->audio.packet_size, - payload->audio.channels, - payload->audio.bitrate, - pc.impl()->load_manager()); - - // Take possession of this pointer - mozilla::ScopedDeletePtr config(config_raw); - - // Instantiate an appropriate conduit - mozilla::RefPtr rx_conduit = - pc.impl()->media()->GetConduit(level, true); - MOZ_ASSERT_IF(rx_conduit, rx_conduit->type() == MediaSessionConduit::AUDIO); - - // The two sides of a send/receive pair of conduits each keep a raw pointer to the other, - // and are responsible for cleanly shutting down. - mozilla::RefPtr conduit = - mozilla::AudioSessionConduit::Create(static_cast(rx_conduit.get())); - if (!conduit || conduit->ConfigureSendMediaCodec(config)) - return VCM_ERROR; - CSFLogError(logTag, "Created audio pipeline audio level %d %d", - attrs->audio_level, attrs->audio_level_id); - - if (!conduit || conduit->EnableAudioLevelExtension(attrs->audio_level, attrs->audio_level_id)) - return VCM_ERROR; - - pc.impl()->media()->AddConduit(level, false, conduit); - mozilla::RefPtr pipeline = - new mozilla::MediaPipelineTransmit( - pc.impl()->GetHandle(), - pc.impl()->GetMainThread().get(), - pc.impl()->GetSTSThread(), - stream->GetMediaStream(), - pc_track_id, - level, - conduit, rtp_flow, rtcp_flow); - - nsresult res = pipeline->Init(); - if (NS_FAILED(res)) { - CSFLogError(logTag, "Failure initializing audio pipeline"); - return VCM_ERROR; - } - CSFLogDebug(logTag, "Created audio pipeline %p, conduit=%p, pc_stream=%d pc_track=%d", - pipeline.get(), conduit.get(), pc_stream_id, pc_track_id); - - - // Now we have all the pieces, create the pipeline - stream->StorePipeline(pc_track_id, pipeline); - + mediaType = "audio"; + err = vcmTxCreateAudioConduit(level, payload, pc, attrs, conduit); } else if (CC_IS_VIDEO(mcap_id)) { - mozilla::VideoCodecConfig *config_raw; - config_raw = new mozilla::VideoCodecConfig( - payload->remote_rtp_pt, - ccsdpCodecName(payload->codec_type), - payload->video.rtcp_fb_types, - payload->video.max_fs, - payload->video.max_fr, - pc.impl()->load_manager()); - - // Take possession of this pointer - mozilla::ScopedDeletePtr config(config_raw); - - // Instantiate an appropriate conduit - mozilla::RefPtr rx_conduit = - pc.impl()->media()->GetConduit(level, true); - MOZ_ASSERT_IF(rx_conduit, rx_conduit->type() == MediaSessionConduit::VIDEO); - - // The two sides of a send/receive pair of conduits each keep a raw pointer to the other, - // and are responsible for cleanly shutting down. - mozilla::RefPtr conduit = - mozilla::VideoSessionConduit::Create(static_cast(rx_conduit.get())); - - // Find the appropriate media conduit config - if (!conduit) - return VCM_ERROR; - - if (vcmEnsureExternalCodec(conduit, config_raw, true)) - return VCM_ERROR; - - if (conduit->ConfigureSendMediaCodec(config)) - return VCM_ERROR; - - pc.impl()->media()->AddConduit(level, false, conduit); - - // Now we have all the pieces, create the pipeline - mozilla::RefPtr pipeline = - new mozilla::MediaPipelineTransmit( - pc.impl()->GetHandle(), - pc.impl()->GetMainThread().get(), - pc.impl()->GetSTSThread(), - stream->GetMediaStream(), - pc_track_id, - level, - conduit, rtp_flow, rtcp_flow); - - nsresult res = pipeline->Init(); - if (NS_FAILED(res)) { - CSFLogError(logTag, "Failure initializing video pipeline"); - return VCM_ERROR; - } - - CSFLogDebug(logTag, "Created video pipeline %p, conduit=%p, pc_stream=%d pc_track=%d", - pipeline.get(), conduit.get(), pc_stream_id, pc_track_id); - - stream->StorePipeline(pc_track_id, pipeline); + mediaType = "video"; + err = vcmTxCreateVideoConduit(level, payload, pc, conduit); } else { CSFLogError(logTag, "%s: mcap_id unrecognized", __FUNCTION__); + } + if (err) { + return err; + } + + pc.impl()->media()->AddConduit(level, false, conduit); + + // Now we have all the pieces, create the pipeline + mozilla::RefPtr pipeline = + new mozilla::MediaPipelineTransmit( + pc.impl()->GetHandle(), + pc.impl()->GetMainThread().get(), + pc.impl()->GetSTSThread(), + stream->GetMediaStream(), + pc_track_id, + level, + conduit, rtp_flow, rtcp_flow); + + nsresult res = pipeline->Init(); + if (NS_FAILED(res)) { + CSFLogError(logTag, "Failure initializing %s pipeline", mediaType); return VCM_ERROR; } +#ifdef MOZILLA_INTERNAL_API + // implement checking for peerIdentity (where failure == black/silence) + nsIDocument* doc = pc.impl()->GetWindow()->GetExtantDoc(); + if (doc) { + pipeline->UpdateSinkIdentity_m(doc->NodePrincipal(), pc.impl()->GetPeerIdentity()); + } else { + CSFLogError(logTag, "Initializing pipeline without attached doc"); + } +#endif + + CSFLogDebug(logTag, + "Created %s pipeline %p, conduit=%p, pc_stream=%d pc_track=%d", + mediaType, pipeline.get(), conduit.get(), + pc_stream_id, pc_track_id); + stream->StorePipeline(pc_track_id, pipeline); // This tells the receive MediaPipeline (if there is one) whether we are // doing bundle, and if so, updates the filter. Once the filter is finalized, @@ -2681,7 +2717,7 @@ int vcmGetVideoCodecList(int request_type) CSFLogDebug( logTag, "%s(codec_mask = %X)", fname, codecMask); //return codecMask; - return VCM_CODEC_RESOURCE_H264; + return VCM_CODEC_RESOURCE_H264; #else int codecMask = VcmSIPCCBinding::getVideoCodecs(); CSFLogDebug(logTag, "GetVideoCodecList returning %X", codecMask); @@ -2715,11 +2751,11 @@ void vcmMediaControl(cc_call_handle_t call_handle, vcm_media_control_to_encoder { if ( to_encoder == VCM_MEDIA_CONTROL_PICTURE_FAST_UPDATE ) { - StreamObserver* obs = VcmSIPCCBinding::getStreamObserver(); - if (obs != nullptr) - { - obs->sendIFrame(call_handle); - } + StreamObserver* obs = VcmSIPCCBinding::getStreamObserver(); + if (obs != nullptr) + { + obs->sendIFrame(call_handle); + } } } @@ -2851,7 +2887,7 @@ cc_boolean vcmCheckAttribs(cc_uint32_t media_type, void *sdp_p, int level, void switch (media_type) { case RTP_VP8: - return TRUE; + return TRUE; case RTP_H264_P0: case RTP_H264_P1: @@ -3011,7 +3047,7 @@ int vcmDtmfBurst(int digit, int duration, int direction) CSFLogDebug( logTag, "vcmDtmfBurst(): digit=%d duration=%d, direction=%d", digit, duration, direction); StreamObserver* obs = VcmSIPCCBinding::getStreamObserver(); if(obs != nullptr) - obs->dtmfBurst(digit, duration, direction); + obs->dtmfBurst(digit, duration, direction); return 0; } diff --git a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp index c3d107dd662..6cbb0af11db 100644 --- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp +++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp @@ -39,6 +39,9 @@ #include "transportlayerice.h" #include "runnable_utils.h" #include "libyuv/convert.h" +#ifdef MOZILLA_INTERNAL_API +#include "mozilla/PeerIdentity.h" +#endif #include "mozilla/gfx/Point.h" #include "mozilla/gfx/Types.h" @@ -653,6 +656,25 @@ nsresult MediaPipelineTransmit::Init() { return MediaPipeline::Init(); } +#ifdef MOZILLA_INTERNAL_API +void MediaPipelineTransmit::UpdateSinkIdentity_m(nsIPrincipal* principal, + const PeerIdentity* sinkIdentity) { + ASSERT_ON_THREAD(main_thread_); + bool enableStream = principal->Subsumes(domstream_->GetPrincipal()); + if (!enableStream) { + // first try didn't work, but there's a chance that this is still available + // if our stream is bound to a peerIdentity, and the peer connection (our + // sink) is bound to the same identity, then we can enable the stream + PeerIdentity* streamIdentity = domstream_->GetPeerIdentity(); + if (sinkIdentity && streamIdentity) { + enableStream = (*sinkIdentity == *streamIdentity); + } + } + + listener_->SetEnabled(enableStream); +} +#endif + nsresult MediaPipelineTransmit::TransportReady_s(TransportInfo &info) { ASSERT_ON_THREAD(sts_thread_); // Call base ready function. @@ -661,7 +683,6 @@ nsresult MediaPipelineTransmit::TransportReady_s(TransportInfo &info) { // Should not be set for a transmitter MOZ_ASSERT(!possible_bundle_rtp_); if (&info == &rtp_) { - // TODO(ekr@rtfm.com): Move onto MSG thread. listener_->SetActive(true); } @@ -935,6 +956,7 @@ NewData(MediaStreamGraph* graph, TrackID tid, // Ignore data in case we have a muxed stream return; } + AudioSegment* audio = const_cast( static_cast(&media)); @@ -950,6 +972,7 @@ NewData(MediaStreamGraph* graph, TrackID tid, // Ignore data in case we have a muxed stream return; } + VideoSegment* video = const_cast( static_cast(&media)); @@ -972,7 +995,7 @@ void MediaPipelineTransmit::PipelineListener::ProcessAudioChunk( // TODO(ekr@rtfm.com): Do more than one channel nsAutoArrayPtr samples(new int16_t[chunk.mDuration]); - if (chunk.mBuffer) { + if (enabled_ && chunk.mBuffer) { switch (chunk.mBufferFormat) { case AUDIO_FORMAT_FLOAT32: { @@ -1081,7 +1104,7 @@ void MediaPipelineTransmit::PipelineListener::ProcessVideoChunk( return; } - if (chunk.mFrame.GetForceBlack()) { + if (!enabled_ || chunk.mFrame.GetForceBlack()) { uint32_t yPlaneLen = size.width*size.height; uint32_t cbcrPlaneLen = yPlaneLen/2; uint32_t length = yPlaneLen + cbcrPlaneLen; diff --git a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h index e333f09420c..5c345c5f3c6 100644 --- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h +++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h @@ -21,6 +21,7 @@ #include "MediaPipelineFilter.h" #include "AudioSegment.h" #include "mozilla/ReentrantMonitor.h" +#include "mozilla/Atomics.h" #include "SrtpFlow.h" #include "databuffer.h" #include "runnable_utils.h" @@ -34,6 +35,8 @@ namespace mozilla { +class PeerIdentity; + // A class that represents the pipeline of audio and video // The dataflow looks like: // @@ -354,7 +357,7 @@ private: // A specialization of pipeline for reading from an input device // and transmitting to the network. class MediaPipelineTransmit : public MediaPipeline { - public: +public: // Set rtcp_transport to nullptr to use rtcp-mux MediaPipelineTransmit(const std::string& pc, nsCOMPtr main_thread, @@ -373,7 +376,14 @@ class MediaPipelineTransmit : public MediaPipeline { {} // Initialize (stuff here may fail) - virtual nsresult Init(); + virtual nsresult Init() MOZ_OVERRIDE; + +#ifdef MOZILLA_INTERNAL_API + // when the principal of the PeerConnection changes, it calls through to here + // so that we can determine whether to enable stream transmission + virtual void UpdateSinkIdentity_m(nsIPrincipal* principal, + const PeerIdentity* sinkIdentity); +#endif // Called on the main thread. virtual void DetachMediaStream() { @@ -395,6 +405,7 @@ class MediaPipelineTransmit : public MediaPipeline { PipelineListener(const RefPtr& conduit) : conduit_(conduit), active_(false), + enabled_(false), direct_connect_(false), samples_10ms_buffer_(nullptr), buffer_current_(0), @@ -416,11 +427,8 @@ class MediaPipelineTransmit : public MediaPipeline { } } - - // XXX. This is not thread-safe but the hazard is just - // that active_ = true takes a while to propagate. Revisit - // when 823600 lands. void SetActive(bool active) { active_ = active; } + void SetEnabled(bool enabled) { enabled_ = enabled; } // Implement MediaStreamListener virtual void NotifyQueuedTrackChanges(MediaStreamGraph* graph, TrackID tid, @@ -451,9 +459,16 @@ class MediaPipelineTransmit : public MediaPipeline { TrackRate rate, VideoChunk& chunk); #endif RefPtr conduit_; - volatile bool active_; + + // active is true if there is a transport to send on + mozilla::Atomic active_; + // enabled is true if the media access control permits sending + // actual content; when false you get black/silence + mozilla::Atomic enabled_; + bool direct_connect_; + // These vars handle breaking audio samples into exact 10ms chunks: // The buffer of 10ms audio samples that we will send once full // (can be carried over from one call to another). @@ -549,7 +564,7 @@ class MediaPipelineReceiveAudio : public MediaPipelineReceive { stream_ = nullptr; } - virtual nsresult Init(); + virtual nsresult Init() MOZ_OVERRIDE; private: // Separate class to allow ref counting @@ -623,7 +638,7 @@ class MediaPipelineReceiveVideo : public MediaPipelineReceive { stream_ = nullptr; } - virtual nsresult Init(); + virtual nsresult Init() MOZ_OVERRIDE; private: class PipelineRenderer : public VideoRenderer { diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp index 27ba25ca6a2..dac266bb2fe 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp @@ -62,6 +62,8 @@ #include "nsNetUtil.h" #include "nsIDOMDataChannel.h" #include "nsIDOMLocation.h" +#include "nsNullPrincipal.h" +#include "mozilla/PeerIdentity.h" #include "mozilla/dom/RTCConfigurationBinding.h" #include "mozilla/dom/RTCStatsReportBinding.h" #include "mozilla/dom/RTCPeerConnectionBinding.h" @@ -474,6 +476,7 @@ PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal) , mSignalingState(PCImplSignalingState::SignalingStable) , mIceConnectionState(PCImplIceConnectionState::New) , mIceGatheringState(PCImplIceGatheringState::New) + , mDtlsConnected(false) , mWindow(nullptr) , mIdentity(nullptr) , mSTSThread(nullptr) @@ -541,18 +544,27 @@ PeerConnectionImpl::~PeerConnectionImpl() } already_AddRefed -PeerConnectionImpl::MakeMediaStream(nsPIDOMWindow* aWindow, - uint32_t aHint) +PeerConnectionImpl::MakeMediaStream(uint32_t aHint) { nsRefPtr stream = - DOMMediaStream::CreateSourceStream(aWindow, aHint); + DOMMediaStream::CreateSourceStream(GetWindow(), aHint); + #ifdef MOZILLA_INTERNAL_API - nsIDocument* doc = aWindow->GetExtantDoc(); - if (!doc) { - return nullptr; - } // Make the stream data (audio/video samples) accessible to the receiving page. - stream->CombineWithPrincipal(doc->NodePrincipal()); + // We're only certain that privacy hasn't been requested if we're connected. + if (mDtlsConnected && !PrivacyRequested()) { + nsIDocument* doc = GetWindow()->GetExtantDoc(); + if (!doc) { + return nullptr; + } + stream->CombineWithPrincipal(doc->NodePrincipal()); + } else { + // we're either certain that we need isolation for the streams, OR + // we're not sure and we can fix the stream in SetDtlsConnected + nsCOMPtr principal = + do_CreateInstance(NS_NULLPRINCIPAL_CONTRACTID); + stream->CombineWithPrincipal(principal); + } #endif CSFLogDebug(logTag, "Created media stream %p, inner: %p", stream.get(), stream->GetStream()); @@ -571,7 +583,7 @@ PeerConnectionImpl::CreateRemoteSourceStreamInfo(nsRefPtr stream = MakeMediaStream(mWindow, 0); + nsRefPtr stream = MakeMediaStream(0); if (!stream) { return NS_ERROR_FAILURE; } @@ -733,6 +745,10 @@ PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, mWindow = aWindow; NS_ENSURE_STATE(mWindow); + if (!aRTCConfiguration->mPeerIdentity.IsEmpty()) { + mPeerIdentity = new PeerIdentity(aRTCConfiguration->mPeerIdentity); + mPrivacyRequested = true; + } #endif // MOZILLA_INTERNAL_API PRTime timestamp = PR_Now(); @@ -924,7 +940,7 @@ PeerConnectionImpl::CreateFakeMediaStream(uint32_t aHint, nsIDOMMediaStream** aR aHint &= ~MEDIA_STREAM_MUTE; } - nsRefPtr stream = MakeMediaStream(mWindow, aHint); + nsRefPtr stream = MakeMediaStream(aHint); if (!stream) { return NS_ERROR_FAILURE; } @@ -1250,6 +1266,17 @@ PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) mTimeCard = nullptr; STAMP_TIMECARD(tc, "Set Local Description"); +#ifdef MOZILLA_INTERNAL_API + nsIDocument* doc = GetWindow()->GetExtantDoc(); + bool isolated = true; + if (doc) { + isolated = mMedia->AnyLocalStreamIsolated(doc->NodePrincipal()); + } else { + CSFLogInfo(logTag, "%s - no document, failing safe", __FUNCTION__); + } + mPrivacyRequested = mPrivacyRequested || isolated; +#endif + mLocalRequestedSDP = aSDP; mInternal->mCall->setLocalDescription((cc_jsep_action_t)aAction, mLocalRequestedSDP, tc); @@ -1385,16 +1412,67 @@ PeerConnectionImpl::CloseStreams() { return NS_OK; } -NS_IMETHODIMP +#ifdef MOZILLA_INTERNAL_API +nsresult +PeerConnectionImpl::SetPeerIdentity(const nsAString& aPeerIdentity) +{ + PC_AUTO_ENTER_API_CALL(true); + MOZ_ASSERT(!aPeerIdentity.IsEmpty()); + + // once set, this can't be changed + if (mPeerIdentity) { + if (!mPeerIdentity->Equals(aPeerIdentity)) { + return NS_ERROR_FAILURE; + } + } else { + mPeerIdentity = new PeerIdentity(aPeerIdentity); + nsIDocument* doc = GetWindow()->GetExtantDoc(); + if (!doc) { + CSFLogInfo(logTag, "Can't update principal on streams; document gone"); + return NS_ERROR_FAILURE; + } + mMedia->UpdateSinkIdentity_m(doc->NodePrincipal(), mPeerIdentity); + } + return NS_OK; +} +#endif + +nsresult +PeerConnectionImpl::SetDtlsConnected(bool aPrivacyRequested) +{ + PC_AUTO_ENTER_API_CALL(false); + + // For this, as with mPrivacyRequested, once we've connected to a peer, we + // fixate on that peer. Dealing with multiple peers or connections is more + // than this run-down wreck of an object can handle. + // Besides, this is only used to say if we have been connected ever. +#ifdef MOZILLA_INTERNAL_API + if (!mPrivacyRequested && !aPrivacyRequested && !mDtlsConnected) { + // now we know that privacy isn't needed for sure + nsIDocument* doc = GetWindow()->GetExtantDoc(); + if (!doc) { + CSFLogInfo(logTag, "Can't update principal on streams; document gone"); + return NS_ERROR_FAILURE; + } + mMedia->UpdateRemoteStreamPrincipals_m(doc->NodePrincipal()); + } +#endif + mDtlsConnected = true; + mPrivacyRequested = mPrivacyRequested || aPrivacyRequested; + return NS_OK; +} + +nsresult PeerConnectionImpl::AddStream(DOMMediaStream &aMediaStream, const MediaConstraintsInternal& aConstraints) { return AddStream(aMediaStream, MediaConstraintsExternal(aConstraints)); } -NS_IMETHODIMP -PeerConnectionImpl::AddStream(DOMMediaStream& aMediaStream, - const MediaConstraintsExternal& aConstraints) { +nsresult +PeerConnectionImpl::AddStream(DOMMediaStream &aMediaStream, + const MediaConstraintsExternal& aConstraints) +{ PC_AUTO_ENTER_API_CALL(true); uint32_t hints = aMediaStream.GetHintContents(); @@ -1421,8 +1499,9 @@ PeerConnectionImpl::AddStream(DOMMediaStream& aMediaStream, uint32_t stream_id; nsresult res = mMedia->AddStream(&aMediaStream, &stream_id); - if (NS_FAILED(res)) + if (NS_FAILED(res)) { return res; + } // TODO(ekr@rtfm.com): these integers should be the track IDs if (hints & DOMMediaStream::HINT_CONTENTS_AUDIO) { diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h index 1d96cb4b51a..6e91a5bd28e 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h @@ -36,6 +36,8 @@ #include "VideoSegment.h" #include "nsNSSShutDown.h" #include "mozilla/dom/RTCStatsReportBinding.h" +#include "nsIPrincipal.h" +#include "mozilla/PeerIdentity.h" #endif namespace test { @@ -114,6 +116,9 @@ using mozilla::DtlsIdentity; using mozilla::ErrorResult; using mozilla::NrIceStunServer; using mozilla::NrIceTurnServer; +#ifdef MOZILLA_INTERNAL_API +using mozilla::PeerIdentity; +#endif class PeerConnectionWrapper; class PeerConnectionMedia; @@ -226,8 +231,7 @@ public: static PeerConnectionImpl* CreatePeerConnection(); static nsresult ConvertRTCConfiguration(const RTCConfiguration& aSrc, IceConfiguration *aDst); - static already_AddRefed MakeMediaStream(nsPIDOMWindow* aWindow, - uint32_t aHint); + already_AddRefed MakeMediaStream(uint32_t aHint); nsresult CreateRemoteSourceStreamInfo(nsRefPtr* aInfo); @@ -277,7 +281,7 @@ public: return mSTSThread; } - // Get the DTLS identity + // Get the DTLS identity (local side) mozilla::RefPtr const GetIdentity() const; std::string GetFingerprint() const; std::string GetFingerprintAlgorithm() const; @@ -371,8 +375,8 @@ public: rv = AddStream(aMediaStream, aConstraints); } - NS_IMETHODIMP AddStream(DOMMediaStream & aMediaStream, - const MediaConstraintsExternal& aConstraints); + nsresult AddStream(DOMMediaStream &aMediaStream, + const MediaConstraintsExternal& aConstraints); NS_IMETHODIMP_TO_ERRORRESULT(RemoveStream, ErrorResult &rv, DOMMediaStream& aMediaStream) @@ -380,6 +384,28 @@ public: rv = RemoveStream(aMediaStream); } + + nsresult GetPeerIdentity(nsAString& peerIdentity) + { +#ifdef MOZILLA_INTERNAL_API + if (mPeerIdentity) { + peerIdentity = mPeerIdentity->ToString(); + return NS_OK; + } +#endif + + peerIdentity.SetIsVoid(true); + return NS_OK; + } + +#ifdef MOZILLA_INTERNAL_API + const PeerIdentity* GetPeerIdentity() const { return mPeerIdentity; } + nsresult SetPeerIdentity(const nsAString& peerIdentity); +#endif + + // this method checks to see if we've made a promise to protect media. + bool PrivacyRequested() const { return mPrivacyRequested; } + NS_IMETHODIMP GetFingerprint(char** fingerprint); void GetFingerprint(nsAString& fingerprint) { @@ -513,6 +539,8 @@ public: void SetSignalingState_m(mozilla::dom::PCImplSignalingState aSignalingState); bool IsClosed() const; + // called when DTLS connects; we only need this once + nsresult SetDtlsConnected(bool aPrivacyRequested); bool HasMedia() const; @@ -608,6 +636,10 @@ private: mozilla::dom::PCImplIceConnectionState mIceConnectionState; mozilla::dom::PCImplIceGatheringState mIceGatheringState; + // DTLS + // this is true if we have been connected ever, see SetDtlsConnected + bool mDtlsConnected; + nsCOMPtr mThread; // TODO: Remove if we ever properly wire PeerConnection for cycle-collection. nsWeakPtr mPCObserver; @@ -625,8 +657,20 @@ private: std::string mFingerprint; std::string mRemoteFingerprint; - // The DTLS identity + // identity-related fields mozilla::RefPtr mIdentity; +#ifdef MOZILLA_INTERNAL_API + // The entity on the other end of the peer-to-peer connection; + // void if they are not yet identified, and no constraint has been set + nsAutoPtr mPeerIdentity; +#endif + // Whether an app should be prevented from accessing media produced by the PC + // If this is true, then media will not be sent until mPeerIdentity matches + // local streams PeerIdentity; and remote streams are protected from content + // + // This can be false if mPeerIdentity is set, in the case where identity is + // provided, but the media is not protected from the app on either side + bool mPrivacyRequested; // A handle to refer to this PC with std::string mHandle; @@ -642,7 +686,7 @@ private: #ifdef MOZILLA_INTERNAL_API // DataConnection that's used to get all the DataChannels - nsRefPtr mDataConnection; + nsRefPtr mDataConnection; #endif nsRefPtr mMedia; diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp index 6cac31f1812..7d51c843442 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp @@ -15,6 +15,7 @@ #include "AudioConduit.h" #include "VideoConduit.h" #include "runnable_utils.h" +#include "transportlayerdtls.h" #ifdef MOZILLA_INTERNAL_API #include "MediaStreamList.h" @@ -137,7 +138,6 @@ PeerConnectionImpl* PeerConnectionImpl::CreatePeerConnection() PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl *parent) : mParent(parent), - mLocalSourceStreamsLock("PeerConnectionMedia.mLocalSourceStreamsLock"), mIceCtx(nullptr), mDNSResolver(new mozilla::NrIceResolver()), mMainThread(mParent->GetMainThread()), @@ -235,6 +235,8 @@ nsresult PeerConnectionMedia::Init(const std::vector& stun_serv nsresult PeerConnectionMedia::AddStream(nsIDOMMediaStream* aMediaStream, uint32_t *stream_id) { + ASSERT_ON_THREAD(mMainThread); + if (!aMediaStream) { CSFLogError(logTag, "%s - aMediaStream is NULL", __FUNCTION__); return NS_ERROR_FAILURE; @@ -263,7 +265,6 @@ PeerConnectionMedia::AddStream(nsIDOMMediaStream* aMediaStream, uint32_t *stream // allow one of each. // TODO(ekr@rtfm.com): remove this when multiple of each stream // is allowed - mozilla::MutexAutoLock lock(mLocalSourceStreamsLock); for (uint32_t u = 0; u < mLocalSourceStreams.Length(); u++) { nsRefPtr localSourceStream = mLocalSourceStreams[u]; @@ -295,13 +296,13 @@ nsresult PeerConnectionMedia::RemoveStream(nsIDOMMediaStream* aMediaStream, uint32_t *stream_id) { MOZ_ASSERT(aMediaStream); + ASSERT_ON_THREAD(mMainThread); DOMMediaStream* stream = static_cast(aMediaStream); CSFLogDebug(logTag, "%s: MediaStream: %p", __FUNCTION__, aMediaStream); - mozilla::MutexAutoLock lock(mLocalSourceStreamsLock); for (uint32_t u = 0; u < mLocalSourceStreams.Length(); u++) { nsRefPtr localSourceStream = mLocalSourceStreams[u]; if (localSourceStream->GetMediaStream() == stream) { @@ -357,6 +358,11 @@ PeerConnectionMedia::ShutdownMediaTransport_s() CSFLogDebug(logTag, "%s: ", __FUNCTION__); + // Here we access m{Local|Remote}SourceStreams off the main thread. + // That's OK because by here PeerConnectionImpl has forgotten about us, + // so there is no chance of getting a call in here from outside. + // The dispatches from SelfDestruct() and to SelfDestruct_m() provide + // memory barriers that protect us from badness. for (uint32_t i=0; i < mLocalSourceStreams.Length(); ++i) { mLocalSourceStreams[i]->DetachTransport_s(); } @@ -377,6 +383,7 @@ PeerConnectionMedia::ShutdownMediaTransport_s() LocalSourceStreamInfo* PeerConnectionMedia::GetLocalStream(int aIndex) { + ASSERT_ON_THREAD(mMainThread); if(aIndex < 0 || aIndex >= (int) mLocalSourceStreams.Length()) { return nullptr; } @@ -388,6 +395,7 @@ PeerConnectionMedia::GetLocalStream(int aIndex) RemoteSourceStreamInfo* PeerConnectionMedia::GetRemoteStream(int aIndex) { + ASSERT_ON_THREAD(mMainThread); if(aIndex < 0 || aIndex >= (int) mRemoteSourceStreams.Length()) { return nullptr; } @@ -472,6 +480,7 @@ nsresult PeerConnectionMedia::AddRemoteStream(nsRefPtr aInfo, int *aIndex) { + ASSERT_ON_THREAD(mMainThread); MOZ_ASSERT(aIndex); *aIndex = mRemoteSourceStreams.Length(); @@ -524,9 +533,113 @@ PeerConnectionMedia::IceStreamReady(NrIceMediaStream *aStream) CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str()); } + void -LocalSourceStreamInfo::StorePipeline(int aTrack, - mozilla::RefPtr aPipeline) +PeerConnectionMedia::DtlsConnected(TransportLayer *dtlsLayer, + TransportLayer::State state) +{ + dtlsLayer->SignalStateChange.disconnect(this); + + bool privacyRequested = false; + // TODO (Bug 952678) set privacy mode, ask the DTLS layer about that + GetMainThread()->Dispatch( + WrapRunnable(mParent, &PeerConnectionImpl::SetDtlsConnected, privacyRequested), + NS_DISPATCH_NORMAL); +} + +void +PeerConnectionMedia::AddTransportFlow(int aIndex, bool aRtcp, + const RefPtr &aFlow) +{ + int index_inner = aIndex * 2 + (aRtcp ? 1 : 0); + + MOZ_ASSERT(!mTransportFlows[index_inner]); + mTransportFlows[index_inner] = aFlow; + + GetSTSThread()->Dispatch( + WrapRunnable(this, &PeerConnectionMedia::ConnectDtlsListener_s, aFlow), + NS_DISPATCH_NORMAL); +} + +void +PeerConnectionMedia::ConnectDtlsListener_s(const RefPtr& aFlow) +{ + TransportLayer* dtls = aFlow->GetLayer(TransportLayerDtls::ID()); + dtls->SignalStateChange.connect(this, &PeerConnectionMedia::DtlsConnected); +} + +#ifdef MOZILLA_INTERNAL_API +/** + * Tells you if any local streams is isolated. Obviously, we want all the + * streams to be isolated equally so that they can all be sent or not. But we + * can't make that determination for certain, because stream principals change. + * Therefore, we check once when we are setting a local description and that + * determines if we flip the "privacy requested" bit on. If a stream cannot be + * sent, then we'll be sending black/silence later; maybe this will correct + * itself and we can send actual content. + * + * @param scriptPrincipal the principal + * @returns true if any stream is isolated + */ +bool +PeerConnectionMedia::AnyLocalStreamIsolated(nsIPrincipal *scriptPrincipal) const +{ + ASSERT_ON_THREAD(mMainThread); + + for (uint32_t u = 0; u < mLocalSourceStreams.Length(); u++) { + // check if we should be asking for a private call for this stream + DOMMediaStream* stream = mLocalSourceStreams[u]->GetMediaStream(); + if (!scriptPrincipal->Subsumes(stream->GetPrincipal())) { + return true; + } + } + return false; +} + +void +PeerConnectionMedia::UpdateRemoteStreamPrincipals_m(nsIPrincipal* aPrincipal) +{ + ASSERT_ON_THREAD(mMainThread); + + for (uint32_t u = 0; u < mRemoteSourceStreams.Length(); u++) { + mRemoteSourceStreams[u]->UpdatePrincipal_m(aPrincipal); + } +} + +void +PeerConnectionMedia::UpdateSinkIdentity_m(nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity) +{ + ASSERT_ON_THREAD(mMainThread); + + for (uint32_t u = 0; u < mLocalSourceStreams.Length(); u++) { + mLocalSourceStreams[u]->UpdateSinkIdentity_m(aPrincipal, aSinkIdentity); + } +} + +void +LocalSourceStreamInfo::UpdateSinkIdentity_m(nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity) +{ + for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) { + MediaPipelineTransmit* pipeline = + static_cast((*it).second.get()); + pipeline->UpdateSinkIdentity_m(aPrincipal, aSinkIdentity); + } +} + +void RemoteSourceStreamInfo::UpdatePrincipal_m(nsIPrincipal* aPrincipal) +{ + // this blasts away the existing principal + // we only do this when we become certain that the stream is safe to make + // accessible to the script principal + mMediaStream->SetPrincipal(aPrincipal); +} +#endif // MOZILLA_INTERNAL_API + +void +LocalSourceStreamInfo::StorePipeline( + int aTrack, mozilla::RefPtr aPipeline) { MOZ_ASSERT(mPipelines.find(aTrack) == mPipelines.end()); if (mPipelines.find(aTrack) != mPipelines.end()) { @@ -539,9 +652,9 @@ LocalSourceStreamInfo::StorePipeline(int aTrack, } void -RemoteSourceStreamInfo::StorePipeline(int aTrack, - bool aIsVideo, - mozilla::RefPtr aPipeline) +RemoteSourceStreamInfo::StorePipeline( + int aTrack, bool aIsVideo, + mozilla::RefPtr aPipeline) { MOZ_ASSERT(mPipelines.find(aTrack) == mPipelines.end()); if (mPipelines.find(aTrack) != mPipelines.end()) { diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h index 5a50c2525ec..fa5bc35bd76 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h @@ -31,8 +31,11 @@ #include "VideoSegment.h" #endif +class nsIPrincipal; + namespace mozilla { class DataChannel; +class PeerIdentity; namespace dom { class RTCInboundRTPStreamStats; class RTCOutboundRTPStreamStats; @@ -180,7 +183,7 @@ public: } SourceStreamInfo(already_AddRefed& aMediaStream, - PeerConnectionMedia *aParent) + PeerConnectionMedia *aParent) : mMediaStream(aMediaStream), mParent(aParent) { MOZ_ASSERT(mMediaStream); @@ -215,7 +218,12 @@ public: DOMMediaStream* GetMediaStream() { return mMediaStream; } - void StorePipeline(int aTrack, mozilla::RefPtr aPipeline); + void StorePipeline(int aTrack, + mozilla::RefPtr aPipeline); + +#ifdef MOZILLA_INTERNAL_API + void UpdateSinkIdentity_m(nsIPrincipal* aPrincipal, const PeerIdentity* aSinkIdentity); +#endif void ExpectAudio(const mozilla::TrackID); void ExpectVideo(const mozilla::TrackID); @@ -243,13 +251,17 @@ class RemoteSourceStreamInfo : public SourceStreamInfo { return mMediaStream; } void StorePipeline(int aTrack, bool aIsVideo, - mozilla::RefPtr aPipeline); + mozilla::RefPtr aPipeline); bool SetUsingBundle_m(int aLevel, bool decision); void DetachTransport_s(); void DetachMedia_m(); +#ifdef MOZILLA_INTERNAL_API + void UpdatePrincipal_m(nsIPrincipal* aPrincipal); +#endif + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteSourceStreamInfo) public: @@ -283,10 +295,10 @@ class PeerConnectionMedia : public sigslot::has_slots<> { return mIceStreams.size(); } - // Add a stream + // Add a stream (main thread only) nsresult AddStream(nsIDOMMediaStream* aMediaStream, uint32_t *stream_id); - // Remove a stream + // Remove a stream (main thread only) nsresult RemoveStream(nsIDOMMediaStream* aMediaStream, uint32_t *stream_id); // Get a specific local stream @@ -312,6 +324,19 @@ class PeerConnectionMedia : public sigslot::has_slots<> { nsresult AddRemoteStream(nsRefPtr aInfo, int *aIndex); nsresult AddRemoteStreamHint(int aIndex, bool aIsVideo); +#ifdef MOZILLA_INTERNAL_API + // In cases where the peer isn't yet identified, we disable the pipeline (not + // the stream, that would potentially affect others), so that it sends + // black/silence. Once the peer is identified, re-enable those streams. + void UpdateSinkIdentity_m(nsIPrincipal* aPrincipal, const PeerIdentity* aSinkIdentity); + // this determines if any stream is isolated, given the current + // document (or script) principal + bool AnyLocalStreamIsolated(nsIPrincipal *scriptPrincipal) const; + // When we finally learn who is on the other end, we need to change the ownership + // on streams + void UpdateRemoteStreamPrincipals_m(nsIPrincipal* aPrincipal); +#endif + const nsCOMPtr& GetMainThread() const { return mMainThread; } const nsCOMPtr& GetSTSThread() const { return mSTSThread; } @@ -329,12 +354,10 @@ class PeerConnectionMedia : public sigslot::has_slots<> { // Add a transport flow void AddTransportFlow(int aIndex, bool aRtcp, - const mozilla::RefPtr &aFlow) { - int index_inner = aIndex * 2 + (aRtcp ? 1 : 0); - - MOZ_ASSERT(!mTransportFlows[index_inner]); - mTransportFlows[index_inner] = aFlow; - } + const mozilla::RefPtr &aFlow); + void ConnectDtlsListener_s(const mozilla::RefPtr& aFlow); + void DtlsConnected(mozilla::TransportLayer* aFlow, + mozilla::TransportLayer::State state); mozilla::RefPtr GetConduit(int aStreamIndex, bool aReceive) { int index_inner = aStreamIndex * 2 + (aReceive ? 0 : 1); @@ -379,10 +402,11 @@ class PeerConnectionMedia : public sigslot::has_slots<> { PeerConnectionImpl *mParent; // A list of streams returned from GetUserMedia - mozilla::Mutex mLocalSourceStreamsLock; + // This is only accessed on the main thread (with one special exception) nsTArray > mLocalSourceStreams; // A list of streams provided by the other side + // This is only accessed on the main thread (with one special exception) nsTArray > mRemoteSourceStreams; // ICE objects diff --git a/media/webrtc/signaling/test/FakeMediaStreams.h b/media/webrtc/signaling/test/FakeMediaStreams.h index d8ff9d2fc0b..a9193c5d070 100644 --- a/media/webrtc/signaling/test/FakeMediaStreams.h +++ b/media/webrtc/signaling/test/FakeMediaStreams.h @@ -90,7 +90,7 @@ class Fake_MediaStream { protected: std::set mListeners; mozilla::Mutex mMutex; // Lock to prevent the listener list from being modified while - // executing Periodic(). + // executing Periodic(). }; class Fake_MediaPeriodic : public nsITimerCallback { @@ -242,6 +242,8 @@ public: uint32_t GetHintContents() const { return mHintContents; } void SetHintContents(uint32_t aHintContents) { mHintContents = aHintContents; } + void SetTrackEnabled(mozilla::TrackID aTrackID, bool aEnabled) {} + private: nsRefPtr mMediaStream;