Bug 942367 - Stream isolation for WebRTC r=bholley
authorMartin Thomson <martin.thomson@gmail.com>
Thu, 01 May 2014 12:51:00 +0200
changeset 181538 50116088c244682f06cb08083e092b7f4fda80f3
parent 181537 8d893585ff9ac25a0e32e883ece0838f6278dd64
child 181539 8c893efb5eb71018c52ccd15740282c2400f4ff7
push id6635
push userryanvm@gmail.com
push dateMon, 05 May 2014 17:24:17 +0000
treeherderfx-team@2897fb554990 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs942367
milestone32.0a1
Bug 942367 - Stream isolation for WebRTC r=bholley
content/media/webrtc/PeerIdentity.cpp
dom/media/MediaManager.cpp
dom/media/PeerConnection.js
dom/media/moz.build
dom/media/tests/identity/mochitest.ini
dom/media/tests/identity/test_getIdentityAssertion.html
dom/media/tests/identity/test_peerConnection_peerIdentity.html
dom/media/tests/mochitest/blacksilence.js
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/test_getUserMedia_peerIdentity.html
media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp
media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
media/webrtc/signaling/test/FakeMediaStreams.h
--- a/content/media/webrtc/PeerIdentity.cpp
+++ b/content/media/webrtc/PeerIdentity.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "PeerIdentity.h"
 
 #include "nsCOMPtr.h"
 #include "nsIIDNService.h"
 #include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
 
 namespace mozilla {
 
 bool
 PeerIdentity::Equals(const PeerIdentity& aOther) const
 {
   return Equals(aOther.mPeerIdentity);
 }
@@ -31,17 +32,17 @@ PeerIdentity::Equals(const nsAString& aO
 
   nsString host;
   GetHost(mPeerIdentity, host);
   nsString otherHost;
   GetHost(aOtherString, otherHost);
 
   nsresult rv;
   nsCOMPtr<nsIIDNService> 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;
   }
 
   nsCString normHost;
   GetNormalizedHost(idnService, host, normHost);
   nsCString normOtherHost;
   GetNormalizedHost(idnService, otherHost, normOtherHost);
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -19,31 +19,34 @@
 #include "nsIPermissionManager.h"
 #include "nsIPopupWindowManager.h"
 #include "nsISupportsArray.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #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"
 #include "mozilla/dom/GetUserMediaRequestBinding.h"
 #include "MediaTrackConstraints.h"
 
 #include "Latency.h"
 
 // For PR_snprintf
 #include "prprf.h"
 
 #include "nsJSUtils.h"
 #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)
 #include "MediaEngineWebRTC.h"
 #endif
 
 #ifdef MOZ_B2G
 #include "MediaPermissionGonk.h"
@@ -489,21 +492,23 @@ class GetUserMediaStreamRunnable : publi
 {
 public:
   GetUserMediaStreamRunnable(
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
     nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
     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);
     mError.swap(aError);
   }
 
   ~GetUserMediaStreamRunnable() {}
 
@@ -616,19 +621,26 @@ public:
     trackunion->mPort = port.forget();
     // Log the relationship between SourceMediaStream and TrackUnion stream
     // Make sure logger starts before capture
     AsyncLatencyLogger::Get(true);
     LogLatency(AsyncLatencyLogger::MediaStreamCreate,
                reinterpret_cast<uint64_t>(stream.get()),
                reinterpret_cast<int64_t>(trackunion->GetStream()));
 
-    trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal());
+    nsCOMPtr<nsIPrincipal> 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).
     mListener->Activate(stream.forget(), mAudioSource, mVideoSource);
 
     // Note: includes JS callbacks; must be released on MainThread
     TracksAvailableCallback* tracksAvailableCallback =
       new TracksAvailableCallback(mManager, mSuccess, mWindowID, trackunion);
@@ -657,16 +669,17 @@ public:
 
 private:
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
   nsRefPtr<MediaEngineSource> mAudioSource;
   nsRefPtr<MediaEngineSource> mVideoSource;
   uint64_t mWindowID;
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
+  nsAutoPtr<PeerIdentity> mPeerIdentity;
   nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
 static bool
 IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
 }
 
@@ -1050,19 +1063,24 @@ public:
         LOG(("Failed to allocate videosource %d\n",rv));
         if (aAudioSource) {
           aAudioSource->Deallocate();
         }
         Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
         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);
     MOZ_ASSERT(!mError);
 
     return;
   }
 
@@ -1291,18 +1309,18 @@ MediaManager::NotifyRecordingStatusChang
 
     requestURL = NS_ConvertUTF8toUTF16(pageURL);
   }
 
   props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
   props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
 
   obs->NotifyObservers(static_cast<nsIPropertyBag2*>(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
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
     unused <<
       dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(aMsg,
                                                                    requestURL,
                                                                    aIsAudio,
@@ -1671,18 +1689,17 @@ MediaManager::RemoveFromWindowList(uint6
     if (window) {
       nsPIDOMWindow *outer = window->GetOuterWindow();
       if (outer) {
         uint64_t outerID = outer->WindowID();
 
         // 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<nsIObserverService> obs = services::GetObserverService();
         obs->NotifyObservers(nullptr, "recording-window-ended", data.get());
         LOG(("Sent recording-window-ended for window %llu (outer %llu)",
              aWindowID, outerID));
       } else {
         LOG(("No outer window for inner %llu", aWindowID));
       }
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -286,18 +286,18 @@ RTCPeerConnection.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer]),
   init: function(win) { this._win = win; },
 
   __init: function(rtcConfig) {
     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");
     if (_globalPCList._networkdown) {
       throw new this._win.DOMError("",
           "Can't create RTCPeerConnections when the network is down");
     }
 
@@ -342,16 +342,29 @@ RTCPeerConnection.prototype = {
   get _impl() {
     if (!this._pc) {
       throw new this._win.DOMError("",
           "RTCPeerConnection is gone (did you enter Offline mode?)");
     }
     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);
     let warningFunc = this.logWarning.bind(this);
     this._localIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc,
                                            this.dispatchEvent.bind(this));
     this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc,
                                             this.dispatchEvent.bind(this));
@@ -651,45 +664,102 @@ RTCPeerConnection.prototype = {
         break;
       case "pranswer":
         throw new this._win.DOMError("", "pranswer not yet implemented");
       default:
         throw new this._win.DOMError("",
             "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],
       wait: true,
       type: desc.type
     });
   },
 
-  _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);
   },
 
   setIdentityProvider: function(provider, protocol, username) {
     this._checkClosed();
     this._localIdp.setIdentityProvider(provider, protocol, username);
   },
@@ -698,24 +768,24 @@ RTCPeerConnection.prototype = {
     let args = { assertion: assertion };
     let ev = new this._win.RTCPeerConnectionIdentityEvent("identityresult", args);
     this.dispatchEvent(ev);
   },
 
   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) {
     throw new this._win.DOMError("", "updateIce not yet implemented");
   },
 
   addIceCandidate: function(cand, onSuccess, onError) {
     if (!cand.candidate && !cand.sdpMLineIndex) {
@@ -959,113 +1029,97 @@ PeerConnectionObserver.prototype = {
   __init: function(dompc) {
     this._dompc = dompc._innerObject;
   },
 
   dispatchEvent: function(event) {
     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;
     pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) {
       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();
   },
 
   onCreateAnswerSuccess: function(sdp) {
     let pc = this._dompc;
     let fp = pc._impl.fingerprint;
     pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) {
       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
         // to setLocal, then trigger a call of onicecandidate here.
         this.foundIceCandidate(null);
     }
 
     this._dompc._executeNext();
   },
 
   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();
   },
 
   onIceCandidate: function(level, mid, candidate) {
     this.foundIceCandidate(new this._dompc._win.mozRTCIceCandidate(
         {
             candidate: candidate,
             sdpMid: mid,
@@ -1156,18 +1210,18 @@ PeerConnectionObserver.prototype = {
         this.foundIceCandidate(null);
       }
     }
   },
 
   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":
         this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState);
         break;
 
       case "IceGatheringState":
         this.handleIceGatheringStateChange(this._dompc._pc.iceGatheringState);
@@ -1191,28 +1245,30 @@ PeerConnectionObserver.prototype = {
     }
   },
 
   onGetStatsSuccess: function(dict) {
     let chromeobj = new RTCStatsReport(this._dompc._win, dict);
     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) {
     this.dispatchEvent(new this._dompc._win.MediaStreamEvent("removestream",
                                                              { stream: stream }));
   },
 
   foundIceCandidate: function(cand) {
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -55,13 +55,14 @@ if CONFIG['MOZ_B2G']:
         'MediaPermissionGonk.cpp',
     ]
 
 FAIL_ON_WARNINGS = True
 
 LOCAL_INCLUDES += [
     '../base',
     '../camera',
+    '/caps/include',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'gklayout'
--- a/dom/media/tests/identity/mochitest.ini
+++ b/dom/media/tests/identity/mochitest.ini
@@ -1,21 +1,23 @@
 [DEFAULT]
 skip-if = e10s
 support-files =
   /.well-known/idp-proxy/idp.html
   /.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]
 skip-if = os == "android" || appname == "b2g"
 [test_getIdentityAssertion.html]
 skip-if = os == "android" || appname == "b2g"
 [test_setIdentityProvider.html]
 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"
--- a/dom/media/tests/identity/test_getIdentityAssertion.html
+++ b/dom/media/tests/identity/test_getIdentityAssertion.html
@@ -21,16 +21,17 @@ function checkIdentity(assertion, identi
   var user = JSON.parse(assertion).username;
   is(user, identity, "id should be '" + identity + "' is '" + user + "'");
 }
 
 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",
     function(test) {
       test.pcLocal._pc.onidpassertionerror = function(e) {
         ok(e, "getIdentityAssertion must fail without provider");
         test.next();
       };
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/identity/test_peerConnection_peerIdentity.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8"/>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="../mochitest/head.js"></script>
+  <script type="application/javascript" src="../mochitest/pc.js"></script>
+  <script type="application/javascript" src="../mochitest/templates.js"></script>
+  <script type="application/javascript" src="../mochitest/blacksilence.js"></script>
+</head>
+<body>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    title: "setIdentityProvider leads to peerIdentity and assertions in SDP"
+  });
+
+var test;
+function theTest() {
+  var id1 = 'someone@test1.example.com';
+  var id2 = 'someone@test2.example.com';
+  test = new PeerConnectionTest({
+    config_pc1: {
+      peerIdentity: id2
+    },
+    config_pc2: {
+      peerIdentity: id1
+    }
+  });
+  test.setMediaConstraints([{
+    audio: true,
+    peerIdentity: id2
+  }, {
+    video: true,
+    peerIdentity: id2
+  }], [{
+    audio: true,
+    fake: true,
+    peerIdentity: id1
+  }, {
+    video: true,
+    fake: true,
+    peerIdentity: id1
+  }]);
+  test.setIdentityProvider(test.pcLocal, 'test1.example.com', 'idp.html');
+  test.setIdentityProvider(test.pcRemote, 'test2.example.com', 'idp.html');
+  test.chain.append([
+  [
+    "PEER_IDENTITY_IS_SET_CORRECTLY",
+    function(test) {
+      // no need to wait to check identity in this case,
+      // setRemoteDescription should wait for the IdP to complete
+      function checkIdentity(pc, pfx, idp, name) {
+        is(pc.peerIdentity.idp, idp, pfx + "IdP is correct");
+        is(pc.peerIdentity.name, name + "@" + idp, pfx + "identity is correct");
+      }
+
+      checkIdentity(test.pcLocal._pc, "local: ", "test2.example.com", "someone");
+      checkIdentity(test.pcRemote._pc, "remote: ", "test1.example.com", "someone");
+      test.next();
+    }
+  ],
+  [
+    "REMOTE_STREAMS_ARE_RESTRICTED",
+    function(test) {
+      var remoteStream = test.pcLocal._pc.getRemoteStreams()[0];
+      var oneDone = false;
+      function done() {
+        if (!oneDone) {
+          oneDone = true;
+          return;
+        }
+        test.next();
+      }
+
+      audioIsSilence(true, remoteStream, done);
+      videoIsBlack(true, remoteStream, done);
+    }
+  ],
+  ]);
+  test.run();
+}
+runTest(theTest);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /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));
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 support-files =
   head.js
   constraints.js
   mediaStreamPlayback.js
   pc.js
   templates.js
   NetworkPreparationChromeScript.js
+  blacksilence.js
 
 [test_dataChannel_basicAudio.html]
 skip-if = toolkit == 'gonk' #Bug 962984 for debug, bug 963244 for opt
 [test_dataChannel_basicAudioVideo.html]
 # Disabled on OS X for bug 930481 timeouts
 skip-if = os == 'mac' || toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) b2g-debug(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_basicAudioVideoCombined.html]
 # Disabled on OS X for bug 930481 timeouts
@@ -37,16 +38,17 @@ skip-if = (toolkit == 'gonk' && debug) #
 [test_getUserMedia_playVideoTwice.html]
 [test_getUserMedia_stopAudioStream.html]
 [test_getUserMedia_stopAudioStreamWithFollowupAudio.html]
 [test_getUserMedia_stopVideoAudioStream.html]
 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
 [test_peerConnection_basicAudioVideo.html]
 skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) b2g-debug(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_basicAudioVideoCombined.html]
 skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) b2g-debug(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_basicVideo.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_peerIdentity.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=942367
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test mozGetUserMedia peerIdentity Constraint</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="blacksilence.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=942367">Test mozGetUserMedia peerIdentity Constraint</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+function theTest() {
+  function testPeerIdentityConstraint(withConstraint, done) {
+    var config = { audio: true, video: true, fake: true };
+    if (withConstraint) {
+      config.peerIdentity = 'user@example.com';
+    }
+    info('getting media with constraints: ' + JSON.stringify(config));
+    navigator.mozGetUserMedia(config, function(stream) {
+      var oneDone = false;
+      function checkDone() {
+        if (oneDone) {
+          done();
+        }
+        oneDone = true;
+      }
+      var cancelAudioCheck = audioIsSilence(withConstraint, stream, checkDone);
+      var cancelVideoCheck = videoIsBlack(withConstraint, stream, checkDone);
+      setTimeout(cancelAudioCheck, 20000);
+      setTimeout(cancelVideoCheck, 20000);
+    }, function(e) {
+      ok(false, 'gUM error: ' + e);
+    });
+  };
+
+  // without constraint
+  testPeerIdentityConstraint(false, function() {
+    // with the constraint
+    testPeerIdentityConstraint(true, SimpleTest.finish.bind(SimpleTest));
+  });
+}
+
+runTest(theTest);
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
--- a/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp
+++ b/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp
@@ -25,16 +25,20 @@
 #include "runnable_utils.h"
 #include "cpr_stdlib.h"
 #include "cpr_string.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/Services.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
+#ifdef MOZILLA_INTERNAL_API
+#include "nsIPrincipal.h"
+#include "nsIDocument.h"
+#endif
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <ssl.h>
 #include <sslproto.h>
 #include <algorithm>
 
 #ifdef MOZ_WEBRTC_OMX
@@ -168,50 +172,50 @@ void VcmSIPCCBinding::CandidateReady(NrI
     MOZ_ASSERT(opaque);
 
     VcmIceOpaque *vcm_opaque = static_cast<VcmIceOpaque *>(opaque);
     CSFLogDebug(logTag, "Candidate ready on call %u, level %u",
                 vcm_opaque->call_handle_, vcm_opaque->level_);
 
     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;
 }
 
 void VcmSIPCCBinding::setAudioCodecs(int codecMask)
 {
   CSFLogDebug(logTag, "SETTING AUDIO: %d", codecMask);
   VcmSIPCCBinding::gAudioCodecMask = codecMask;
@@ -566,17 +570,17 @@ static short vcmGetIceStream_m(cc_mcapid
  *  @param[out] port_allocatedp - the ICE default port
  *  @param[out] candidatesp - the ICE candidate array
  *  @param[out] candidate_ctp length of the array
  *
  *  @return 0 for success; VCM_ERROR for failure
  *
  */
 static short vcmRxAllocICE_s(TemporaryRef<NrIceCtx> ctx_in,
-			     TemporaryRef<NrIceMediaStream> stream_in,
+                             TemporaryRef<NrIceMediaStream> stream_in,
                              cc_call_handle_t  call_handle,
                              cc_streamid_t stream_id,
                              uint16_t level,
                              char **default_addrp, /* Out */
                              int *default_portp, /* Out */
                              char ***candidatesp, /* Out */
                              int *candidate_ctp /* Out */
 )
@@ -1335,17 +1339,17 @@ short vcmRxOpen(cc_mcapid_t mcap_id,
                 int *port_allocated)
 {
     char fname[] = "vcmRxOpen";
 
     char dottedIP[20] = "";
     *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 );
     }
 
     CSFLogDebug( logTag, "%s: group_id=%d call_handle=%d listen=%s:%d is_mcast=%d",
                       fname, group_id, call_handle, dottedIP, port_requested, is_multicast);
 
     switch ( mcap_id )
@@ -1646,17 +1650,17 @@ static int vcmRxStartICE_m(cc_mcapid_t m
         pc.impl()->load_manager());
       configs.push_back(config_raw);
     }
 
     if (conduit->ConfigureRecvMediaCodecs(configs))
       return VCM_ERROR;
 
     // Now we have all the pieces, create the pipeline
-    mozilla::RefPtr<mozilla::MediaPipeline> pipeline =
+    mozilla::RefPtr<mozilla::MediaPipelineReceiveAudio> pipeline =
       new mozilla::MediaPipelineReceiveAudio(
         pc.impl()->GetHandle(),
         pc.impl()->GetMainThread().get(),
         pc.impl()->GetSTSThread(),
         stream->GetMediaStream()->GetStream(),
         pc_track_id,
         level,
         conduit,
@@ -1707,17 +1711,17 @@ static int vcmRxStartICE_m(cc_mcapid_t m
       }
       configs.push_back(config_raw);
     }
 
     if (conduit->ConfigureRecvMediaCodecs(configs))
       return VCM_ERROR;
 
     // Now we have all the pieces, create the pipeline
-    mozilla::RefPtr<mozilla::MediaPipeline> pipeline =
+    mozilla::RefPtr<mozilla::MediaPipelineReceiveVideo> pipeline =
         new mozilla::MediaPipelineReceiveVideo(
             pc.impl()->GetHandle(),
             pc.impl()->GetMainThread().get(),
             pc.impl()->GetSTSThread(),
             stream->GetMediaStream()->GetStream(),
             pc_track_id,
             level,
             conduit,
@@ -1882,17 +1886,17 @@ void vcmRxReleasePort  (cc_mcapid_t mcap
     else if(CC_IS_VIDEO(mcap_id))
     {
         if ( VcmSIPCCBinding::getVideoTermination() != nullptr )
            VcmSIPCCBinding::getVideoTermination()->rxRelease( group_id, stream_id, port );
     }
 
     StreamObserver* obs = VcmSIPCCBinding::getStreamObserver();
     if(obs != nullptr)
-    	obs->deregisterStream(call_handle, stream_id);
+        obs->deregisterStream(call_handle, stream_id);
 }
 
 /*
  *  Function:map_tone_type
  *
  *  Description: Convert to corresponding JPhone tone.
  *
  *  Parameters:  tone - vcm tone
@@ -2267,202 +2271,234 @@ int vcmTxStart(cc_mcapid_t mcap_id,
         break;
 
     default:
         break;
     }
     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<mozilla::MediaSessionConduit> &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<mozilla::AudioCodecConfig> config(config_raw);
+
+  // Instantiate an appropriate conduit
+  mozilla::RefPtr<mozilla::MediaSessionConduit> 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<mozilla::AudioSessionConduit> tx_conduit =
+    mozilla::AudioSessionConduit::Create(
+      static_cast<AudioSessionConduit *>(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<mozilla::MediaSessionConduit> &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<mozilla::VideoCodecConfig> config(config_raw);
+
+  // Instantiate an appropriate conduit
+  mozilla::RefPtr<mozilla::MediaSessionConduit> 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<mozilla::VideoSessionConduit> tx_conduit =
+    mozilla::VideoSessionConduit::Create(static_cast<VideoSessionConduit *>(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
  *  Same concept as vcmTxStart but for ICE/PeerConnection-based flows
  *
  *  @param[in]   mcap_id      - media cap id
  *  @param[in]   group_id     - group identifier to which the stream belongs
  *  @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
  *  @param[in]   tos          - bit marking
  *  @param[in]   setup_type   - whether playing the client or server role
  *  @param[in]   fingerprint_alg - the DTLS fingerprint algorithm
  *  @param[in]   fingerprint  - the DTLS fingerprint
  *  @param[in]   attrs        - media attributes
  *
  *  Returns: zero(0) for success; otherwise, ERROR for failure
  *
  */
 #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,
               pc_stream_id,
               level);
 
   // Find the PC and get the stream
   sipcc::PeerConnectionWrapper pc(peerconnection);
   ENSURE_PC(pc, VCM_ERROR);
-  nsRefPtr<sipcc::LocalSourceStreamInfo> stream = pc.impl()->media()->
-    GetLocalStream(pc_stream_id);
+  nsRefPtr<sipcc::LocalSourceStreamInfo> stream =
+    pc.impl()->media()->GetLocalStream(pc_stream_id);
 
   // Create the transport flows
   mozilla::RefPtr<TransportFlow> 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<TransportFlow> 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) {
       CSFLogError( logTag, "Could not create RTCP flow");
       return VCM_ERROR;
     }
   }
 
-
+  const char *mediaType;
+  mozilla::RefPtr<mozilla::MediaSessionConduit> 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<mozilla::AudioCodecConfig> config(config_raw);
-
-    // Instantiate an appropriate conduit
-    mozilla::RefPtr<mozilla::MediaSessionConduit> 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<mozilla::AudioSessionConduit> conduit =
-      mozilla::AudioSessionConduit::Create(static_cast<AudioSessionConduit *>(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<mozilla::MediaPipeline> 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<mozilla::VideoCodecConfig> config(config_raw);
-
-    // Instantiate an appropriate conduit
-    mozilla::RefPtr<mozilla::MediaSessionConduit> 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<mozilla::VideoSessionConduit> conduit =
-      mozilla::VideoSessionConduit::Create(static_cast<VideoSessionConduit *>(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<mozilla::MediaPipeline> 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<mozilla::MediaPipelineTransmit> 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,
   // it is then copied to the transmit pipeline so it can filter RTCP.
   if (attrs->bundle_level) {
     nsAutoPtr<mozilla::MediaPipelineFilter> filter (new MediaPipelineFilter);
     for (int s = 0; s < attrs->num_ssrcs; ++s) {
       filter->AddRemoteSSRC(attrs->ssrcs[s]);
@@ -2676,17 +2712,17 @@ int vcmGetVideoCodecList(int request_typ
     retVal = VcmSIPCCBinding::getVideoTermination() ? VcmSIPCCBinding::getVideoTermination()->getCodecList( map_codec_request_type(request_type) ) : 0;
 
     if ( retVal & VideoCodecMask_H264 )    codecMask |= DSP_H264;
     if ( retVal & VideoCodecMask_H263 )    codecMask |= DSP_H263;
 
     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);
 
   return codecMask;
 #endif
 }
 
@@ -2710,21 +2746,21 @@ int vcmGetVideoMaxSupportedPacketization
  *  @return  void
  *
  */
 
 void vcmMediaControl(cc_call_handle_t  call_handle, vcm_media_control_to_encoder_t 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);
+        }
     }
 }
 
 /**
  * Get the rx/tx stream statistics associated with the call.
  *
  * @param[in]  mcap_id  - media type (audio/video)
  * @param[in]  group_id - group id of the stream
@@ -2846,17 +2882,17 @@ cc_boolean vcmCheckAttribs(cc_uint32_t m
     uint32_t        t_uint;
     struct h264_video *rcap;
 
     *rcapptr = nullptr;
 
     switch (media_type)
     {
     case RTP_VP8:
-    	return TRUE;
+        return TRUE;
 
     case RTP_H264_P0:
     case RTP_H264_P1:
 
         rcap = (struct h264_video *) cpr_malloc( sizeof(struct h264_video) );
         if ( rcap == nullptr )
         {
             CSFLogDebug( logTag, "vcmCheckAttribs(): Malloc Failed for rcap");
@@ -3006,17 +3042,17 @@ void vcmPopulateAttribs(void *sdp_p, int
  *
  * @return void
  */
 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;
 }
 
 /**
  * vcmGetILBCMode
  *
  * This method should return the mode that needs to be used in
  * SDP
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -34,16 +34,19 @@
 #include "MediaSegment.h"
 #include "databuffer.h"
 #include "transportflow.h"
 #include "transportlayer.h"
 #include "transportlayerdtls.h"
 #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"
 
 #include "webrtc/modules/interface/module_common_types.h"
 #include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
@@ -648,25 +651,43 @@ nsresult MediaPipelineTransmit::Init() {
   // unqueued (and not resampled) data
   if (domstream_->AddDirectListener(listener_)) {
     listener_->direct_connect_ = true;
   }
 
   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.
   MediaPipeline::TransportReady_s(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);
   }
 
   return NS_OK;
 }
 
 void MediaPipeline::DisconnectTransport_s(TransportInfo &info) {
   MOZ_ASSERT(info.transport_);
@@ -930,31 +951,33 @@ NewData(MediaStreamGraph* graph, TrackID
   // TODO(ekr@rtfm.com): For now assume that we have only one
   // track type and it's destined for us
   // See bug 784517
   if (media.GetType() == MediaSegment::AUDIO) {
     if (conduit_->type() != MediaSessionConduit::AUDIO) {
       // Ignore data in case we have a muxed stream
       return;
     }
+
     AudioSegment* audio = const_cast<AudioSegment *>(
         static_cast<const AudioSegment *>(&media));
 
     AudioSegment::ChunkIterator iter(*audio);
     while(!iter.IsEnded()) {
       ProcessAudioChunk(static_cast<AudioSessionConduit*>(conduit_.get()),
                         rate, *iter);
       iter.Next();
     }
   } else if (media.GetType() == MediaSegment::VIDEO) {
 #ifdef MOZILLA_INTERNAL_API
     if (conduit_->type() != MediaSessionConduit::VIDEO) {
       // Ignore data in case we have a muxed stream
       return;
     }
+
     VideoSegment* video = const_cast<VideoSegment *>(
         static_cast<const VideoSegment *>(&media));
 
     VideoSegment::ChunkIterator iter(*video);
     while(!iter.IsEnded()) {
       ProcessVideoChunk(static_cast<VideoSessionConduit*>(conduit_.get()),
                         rate, *iter);
       iter.Next();
@@ -967,17 +990,17 @@ NewData(MediaStreamGraph* graph, TrackID
 
 void MediaPipelineTransmit::PipelineListener::ProcessAudioChunk(
     AudioSessionConduit *conduit,
     TrackRate rate,
     AudioChunk& chunk) {
   // TODO(ekr@rtfm.com): Do more than one channel
   nsAutoArrayPtr<int16_t> samples(new int16_t[chunk.mDuration]);
 
-  if (chunk.mBuffer) {
+  if (enabled_ && chunk.mBuffer) {
     switch (chunk.mBufferFormat) {
       case AUDIO_FORMAT_FLOAT32:
         {
           const float* buf = static_cast<const float *>(chunk.mChannelData[0]);
           ConvertAudioSamplesWithScale(buf, static_cast<int16_t*>(samples),
                                        chunk.mDuration, chunk.mVolume);
         }
         break;
@@ -1076,17 +1099,17 @@ void MediaPipelineTransmit::PipelineList
   }
 
   gfx::IntSize size = img->GetSize();
   if ((size.width & 1) != 0 || (size.height & 1) != 0) {
     MOZ_ASSERT(false, "Can't handle odd-sized images");
     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;
 
     // Send a black image.
     nsAutoArrayPtr<uint8_t> pixelData;
     static const fallible_t fallible = fallible_t();
     pixelData = new (fallible) uint8_t[length];
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
@@ -16,29 +16,32 @@
 #include "DOMMediaStream.h"
 #include "MediaStreamGraph.h"
 #include "VideoUtils.h"
 #endif
 #include "MediaConduitInterface.h"
 #include "MediaPipelineFilter.h"
 #include "AudioSegment.h"
 #include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Atomics.h"
 #include "SrtpFlow.h"
 #include "databuffer.h"
 #include "runnable_utils.h"
 #include "transportflow.h"
 
 #ifdef MOZILLA_INTERNAL_API
 #include "VideoSegment.h"
 #endif
 
 #include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
 
 namespace mozilla {
 
+class PeerIdentity;
+
 // A class that represents the pipeline of audio and video
 // The dataflow looks like:
 //
 // TRANSMIT
 // CaptureDevice -> stream -> [us] -> conduit -> [us] -> transport -> network
 //
 // RECEIVE
 // network -> transport -> [us] -> conduit -> [us] -> stream -> Playout
@@ -349,17 +352,17 @@ public:
   NS_IMETHOD Run() { return NS_OK; }
 private:
   RefPtr<MediaSessionConduit> mConduit;
 };
 
 // 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<nsIEventTarget> main_thread,
                         nsCOMPtr<nsIEventTarget> sts_thread,
                         DOMMediaStream *domstream,
                         TrackID track_id,
                         int level,
                         RefPtr<MediaSessionConduit> conduit,
@@ -368,17 +371,24 @@ class MediaPipelineTransmit : public Med
       MediaPipeline(pc, TRANSMIT, main_thread, sts_thread,
                     domstream->GetStream(), track_id, level,
                     conduit, rtp_transport, rtcp_transport),
       listener_(new PipelineListener(conduit)),
       domstream_(domstream)
   {}
 
   // 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() {
     ASSERT_ON_THREAD(main_thread_);
     domstream_->RemoveDirectListener(listener_);
     domstream_ = nullptr;
     stream_->RemoveListener(listener_);
     // Let the listener be destroyed with the pipeline (or later).
@@ -390,16 +400,17 @@ class MediaPipelineTransmit : public Med
 
   // Separate class to allow ref counting
   class PipelineListener : public MediaStreamDirectListener {
    friend class MediaPipelineTransmit;
    public:
     PipelineListener(const RefPtr<MediaSessionConduit>& conduit)
       : conduit_(conduit),
         active_(false),
+        enabled_(false),
         direct_connect_(false),
         samples_10ms_buffer_(nullptr),
         buffer_current_(0),
         samplenum_10ms_(0)
 #ifdef MOZILLA_INTERNAL_API
         , last_img_(-1)
 #endif // MOZILLA_INTERNAL_API
     {
@@ -411,21 +422,18 @@ class MediaPipelineTransmit : public Med
       nsresult rv = NS_DispatchToMainThread(new
         ConduitDeleteEvent(conduit_.forget()), NS_DISPATCH_NORMAL);
       MOZ_ASSERT(!NS_FAILED(rv),"Could not dispatch conduit shutdown to main");
       if (NS_FAILED(rv)) {
         MOZ_CRASH();
       }
     }
 
-
-    // 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,
                                           TrackRate rate,
                                           TrackTicks offset,
                                           uint32_t events,
                                           const MediaSegment& queued_media) MOZ_OVERRIDE;
     virtual void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) MOZ_OVERRIDE {}
@@ -446,19 +454,26 @@ class MediaPipelineTransmit : public Med
 
     virtual void ProcessAudioChunk(AudioSessionConduit *conduit,
                                    TrackRate rate, AudioChunk& chunk);
 #ifdef MOZILLA_INTERNAL_API
     virtual void ProcessVideoChunk(VideoSessionConduit *conduit,
                                    TrackRate rate, VideoChunk& chunk);
 #endif
     RefPtr<MediaSessionConduit> conduit_;
-    volatile bool active_;
+
+    // active is true if there is a transport to send on
+    mozilla::Atomic<bool> active_;
+    // enabled is true if the media access control permits sending
+    // actual content; when false you get black/silence
+    mozilla::Atomic<bool> 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).
     nsAutoArrayPtr<int16_t> samples_10ms_buffer_;
     // The location of the pointer within that buffer (in units of samples).
     int64_t buffer_current_;
     // The number of samples in a 10ms audio chunk.
     int64_t samplenum_10ms_;
@@ -544,17 +559,17 @@ class MediaPipelineReceiveAudio : public
 
   virtual void DetachMediaStream() {
     ASSERT_ON_THREAD(main_thread_);
     listener_->EndTrack();
     stream_->RemoveListener(listener_);
     stream_ = nullptr;
   }
 
-  virtual nsresult Init();
+  virtual nsresult Init() MOZ_OVERRIDE;
 
  private:
   // Separate class to allow ref counting
   class PipelineListener : public GenericReceiveListener {
    public:
     PipelineListener(SourceMediaStream * source, TrackID track_id,
                      const RefPtr<MediaSessionConduit>& conduit);
 
@@ -618,17 +633,17 @@ class MediaPipelineReceiveVideo : public
     // and PipelineListener - the renderer has a raw ptr to the Pipeline to
     // avoid cycles, and the render callbacks are invoked from a different
     // thread so simple null-checks would cause TSAN bugs without locks.
     static_cast<VideoSessionConduit*>(conduit_.get())->DetachRenderer();
     stream_->RemoveListener(listener_);
     stream_ = nullptr;
   }
 
-  virtual nsresult Init();
+  virtual nsresult Init() MOZ_OVERRIDE;
 
  private:
   class PipelineRenderer : public VideoRenderer {
    public:
     PipelineRenderer(MediaPipelineReceiveVideo *pipeline) :
       pipeline_(pipeline) {}
 
     void Detach() { pipeline_ = nullptr; }
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -57,16 +57,18 @@
 #include "nsDOMJSUtils.h"
 #include "nsIDocument.h"
 #include "nsIScriptError.h"
 #include "nsPrintfCString.h"
 #include "nsURLHelper.h"
 #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"
 #include "mozilla/dom/PeerConnectionImplBinding.h"
 #include "mozilla/dom/DataChannelBinding.h"
 #include "MediaStreamList.h"
 #include "MediaStreamTrack.h"
 #include "AudioStreamTrack.h"
@@ -469,16 +471,17 @@ struct PeerConnectionImpl::Internal {
 PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal)
 : mTimeCard(PR_LOG_TEST(signalingLogInfo(),PR_LOG_ERROR) ?
             create_timecard() : nullptr)
   , mInternal(new Internal())
   , mReadyState(PCImplReadyState::New)
   , mSignalingState(PCImplSignalingState::SignalingStable)
   , mIceConnectionState(PCImplIceConnectionState::New)
   , mIceGatheringState(PCImplIceGatheringState::New)
+  , mDtlsConnected(false)
   , mWindow(nullptr)
   , mIdentity(nullptr)
   , mSTSThread(nullptr)
   , mLoadManager(nullptr)
   , mMedia(nullptr)
   , mNumAudioStreams(0)
   , mNumVideoStreams(0)
   , mHaveDataStream(false)
@@ -536,28 +539,37 @@ PeerConnectionImpl::~PeerConnectionImpl(
 
   // Right now, we delete PeerConnectionCtx at XPCOM shutdown only, but we
   // probably want to shut it down more aggressively to save memory.  We
   // could shut down here when there are no uses.  It might be more optimal
   // to release off a timer (and XPCOM Shutdown) to avoid churn
 }
 
 already_AddRefed<DOMMediaStream>
-PeerConnectionImpl::MakeMediaStream(nsPIDOMWindow* aWindow,
-                                    uint32_t aHint)
+PeerConnectionImpl::MakeMediaStream(uint32_t aHint)
 {
   nsRefPtr<DOMMediaStream> 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.
+  // 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<nsIPrincipal> principal =
+      do_CreateInstance(NS_NULLPRINCIPAL_CONTRACTID);
+    stream->CombineWithPrincipal(principal);
   }
-  // Make the stream data (audio/video samples) accessible to the receiving page.
-  stream->CombineWithPrincipal(doc->NodePrincipal());
 #endif
 
   CSFLogDebug(logTag, "Created media stream %p, inner: %p", stream.get(), stream->GetStream());
 
   return stream.forget();
 }
 
 nsresult
@@ -566,17 +578,17 @@ PeerConnectionImpl::CreateRemoteSourceSt
 {
   MOZ_ASSERT(aInfo);
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
 
   // We need to pass a dummy hint here because FakeMediaStream currently
   // needs to actually propagate a hint for local streams.
   // TODO(ekr@rtfm.com): Clean up when we have explicit track lists.
   // See bug 834835.
-  nsRefPtr<DOMMediaStream> stream = MakeMediaStream(mWindow, 0);
+  nsRefPtr<DOMMediaStream> stream = MakeMediaStream(0);
   if (!stream) {
     return NS_ERROR_FAILURE;
   }
 
   static_cast<SourceMediaStream*>(stream->GetStream())->SetPullEnabled(true);
 
   nsRefPtr<RemoteSourceStreamInfo> remote;
   remote = new RemoteSourceStreamInfo(stream.forget(), mMedia);
@@ -728,16 +740,20 @@ PeerConnectionImpl::Initialize(PeerConne
   }
 
   // Currently no standalone unit tests for DataChannel,
   // which is the user of mWindow
   MOZ_ASSERT(aWindow);
   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();
   // Ok if we truncate this.
   char temp[128];
 
 #ifdef MOZILLA_INTERNAL_API
   nsAutoCString locationCStr;
@@ -919,17 +935,17 @@ PeerConnectionImpl::CreateFakeMediaStrea
   bool mute = false;
 
   // Hack to allow you to mute the stream
   if (aHint & MEDIA_STREAM_MUTE) {
     mute = true;
     aHint &= ~MEDIA_STREAM_MUTE;
   }
 
-  nsRefPtr<DOMMediaStream> stream = MakeMediaStream(mWindow, aHint);
+  nsRefPtr<DOMMediaStream> stream = MakeMediaStream(aHint);
   if (!stream) {
     return NS_ERROR_FAILURE;
   }
 
   if (!mute) {
     if (aHint & DOMMediaStream::HINT_CONTENTS_AUDIO) {
       new Fake_AudioGenerator(stream);
     } else {
@@ -1245,16 +1261,27 @@ PeerConnectionImpl::SetLocalDescription(
     CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__);
     return NS_ERROR_FAILURE;
   }
 
   Timecard *tc = mTimeCard;
   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);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP)
@@ -1380,26 +1407,77 @@ PeerConnectionImpl::CloseStreams() {
   }
 
   CSFLogInfo(logTag, "%s: Ending associated call", __FUNCTION__);
 
   mInternal->mCall->endCall();
   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();
 
   // XXX Remove this check once addStream has an error callback
   // available and/or we have plumbing to handle multiple
   // local audio streams.
   if ((hints & DOMMediaStream::HINT_CONTENTS_AUDIO) &&
@@ -1416,18 +1494,19 @@ PeerConnectionImpl::AddStream(DOMMediaSt
       mNumVideoStreams > 0) {
     CSFLogError(logTag, "%s: Only one local video stream is supported for now",
                 __FUNCTION__);
     return NS_ERROR_FAILURE;
   }
 
   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) {
     cc_media_constraints_t* cc_constraints = aConstraints.build();
     NS_ENSURE_TRUE(cc_constraints, NS_ERROR_UNEXPECTED);
     mInternal->mCall->addStream(stream_id, 0, AUDIO, cc_constraints);
     mNumAudioStreams++;
   }
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -31,16 +31,18 @@
 
 #ifdef MOZILLA_INTERNAL_API
 #include "mozilla/TimeStamp.h"
 #include "mozilla/net/DataChannel.h"
 #include "VideoUtils.h"
 #include "VideoSegment.h"
 #include "nsNSSShutDown.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
+#include "nsIPrincipal.h"
+#include "mozilla/PeerIdentity.h"
 #endif
 
 namespace test {
 #ifdef USE_FAKE_PCOBSERVER
 class AFakePCObserver;
 #endif
 }
 
@@ -109,16 +111,19 @@ using mozilla::dom::MediaConstraintsInte
 using mozilla::MediaConstraintsExternal;
 using mozilla::DOMMediaStream;
 using mozilla::NrIceCtx;
 using mozilla::NrIceMediaStream;
 using mozilla::DtlsIdentity;
 using mozilla::ErrorResult;
 using mozilla::NrIceStunServer;
 using mozilla::NrIceTurnServer;
+#ifdef MOZILLA_INTERNAL_API
+using mozilla::PeerIdentity;
+#endif
 
 class PeerConnectionWrapper;
 class PeerConnectionMedia;
 class RemoteSourceStreamInfo;
 class OnCallEventArgs;
 
 class IceConfiguration
 {
@@ -221,18 +226,17 @@ public:
   virtual JSObject* WrapObject(JSContext* cx);
 #endif
 
   static already_AddRefed<PeerConnectionImpl>
       Constructor(const mozilla::dom::GlobalObject& aGlobal, ErrorResult& rv);
   static PeerConnectionImpl* CreatePeerConnection();
   static nsresult ConvertRTCConfiguration(const RTCConfiguration& aSrc,
                                           IceConfiguration *aDst);
-  static already_AddRefed<DOMMediaStream> MakeMediaStream(nsPIDOMWindow* aWindow,
-                                                          uint32_t aHint);
+  already_AddRefed<DOMMediaStream> MakeMediaStream(uint32_t aHint);
 
   nsresult CreateRemoteSourceStreamInfo(nsRefPtr<RemoteSourceStreamInfo>* aInfo);
 
   // Implementation of the only observer we need
   void onCallEvent(const OnCallEventArgs &args);
 
   // DataConnection observers
   void NotifyConnection();
@@ -272,17 +276,17 @@ public:
   }
 
   // Get the STS thread
   nsCOMPtr<nsIEventTarget> GetSTSThread() {
     PC_AUTO_ENTER_API_CALL_NO_CHECK();
     return mSTSThread;
   }
 
-  // Get the DTLS identity
+  // Get the DTLS identity (local side)
   mozilla::RefPtr<DtlsIdentity> const GetIdentity() const;
   std::string GetFingerprint() const;
   std::string GetFingerprintAlgorithm() const;
   std::string GetFingerprintHexValue() const;
 
   // Create a fake media stream
   nsresult CreateFakeMediaStream(uint32_t hint, nsIDOMMediaStream** retval);
 
@@ -366,25 +370,47 @@ public:
 
   NS_IMETHODIMP_TO_ERRORRESULT(AddStream, ErrorResult &rv,
                                DOMMediaStream& aMediaStream,
                                const MediaConstraintsInternal& aConstraints)
   {
     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)
   {
     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)
   {
     char *tmp;
     GetFingerprint(&tmp);
     fingerprint.AssignASCII(tmp);
     delete[] tmp;
   }
@@ -508,16 +534,18 @@ public:
 
   // Called to retreive the list of parsing errors.
   const std::vector<std::string> &GetSdpParseErrors();
 
   // Sets the RTC Signaling State
   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;
 
 #ifdef MOZILLA_INTERNAL_API
   // initialize telemetry for when calls start
   void startCallTelem();
 
   nsresult BuildStatsQuery_m(
@@ -603,16 +631,20 @@ private:
   mozilla::ScopedDeletePtr<Internal> mInternal;
   mozilla::dom::PCImplReadyState mReadyState;
   mozilla::dom::PCImplSignalingState mSignalingState;
 
   // ICE State
   mozilla::dom::PCImplIceConnectionState mIceConnectionState;
   mozilla::dom::PCImplIceGatheringState mIceGatheringState;
 
+  // DTLS
+  // this is true if we have been connected ever, see SetDtlsConnected
+  bool mDtlsConnected;
+
   nsCOMPtr<nsIThread> mThread;
   // TODO: Remove if we ever properly wire PeerConnection for cycle-collection.
   nsWeakPtr mPCObserver;
 
   nsCOMPtr<nsPIDOMWindow> mWindow;
 
   // The SDP sent in from JS - here for debugging.
   std::string mLocalRequestedSDP;
@@ -620,34 +652,46 @@ private:
   // The SDP we are using.
   std::string mLocalSDP;
   std::string mRemoteSDP;
 
   // DTLS fingerprint
   std::string mFingerprint;
   std::string mRemoteFingerprint;
 
-  // The DTLS identity
+  // identity-related fields
   mozilla::RefPtr<DtlsIdentity> 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<PeerIdentity> 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;
 
   // A name for this PC that we are willing to expose to content.
   std::string mName;
 
   // The target to run stuff on
   nsCOMPtr<nsIEventTarget> mSTSThread;
 
   // CPU Load adaptation stuff
   mozilla::LoadManager* mLoadManager;
 
 #ifdef MOZILLA_INTERNAL_API
   // DataConnection that's used to get all the DataChannels
-	nsRefPtr<mozilla::DataChannelConnection> mDataConnection;
+  nsRefPtr<mozilla::DataChannelConnection> mDataConnection;
 #endif
 
   nsRefPtr<PeerConnectionMedia> mMedia;
 
 #ifdef MOZILLA_INTERNAL_API
   // Start time of ICE, used for telemetry
   mozilla::TimeStamp mIceStartTime;
   // Start time of call used for Telemetry
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -10,16 +10,17 @@
 
 #include "nricectx.h"
 #include "nricemediastream.h"
 #include "PeerConnectionImpl.h"
 #include "PeerConnectionMedia.h"
 #include "AudioConduit.h"
 #include "VideoConduit.h"
 #include "runnable_utils.h"
+#include "transportlayerdtls.h"
 
 #ifdef MOZILLA_INTERNAL_API
 #include "MediaStreamList.h"
 #include "nsIScriptGlobalObject.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
 #endif
 
@@ -132,17 +133,16 @@ PeerConnectionImpl* PeerConnectionImpl::
   CSFLogDebug(logTag, "Created PeerConnection: %p", pc);
 
   return pc;
 }
 
 
 PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl *parent)
     : mParent(parent),
-      mLocalSourceStreamsLock("PeerConnectionMedia.mLocalSourceStreamsLock"),
       mIceCtx(nullptr),
       mDNSResolver(new mozilla::NrIceResolver()),
       mMainThread(mParent->GetMainThread()),
       mSTSThread(mParent->GetSTSThread()) {}
 
 nsresult PeerConnectionMedia::Init(const std::vector<NrIceStunServer>& stun_servers,
                                    const std::vector<NrIceTurnServer>& turn_servers)
 {
@@ -230,16 +230,18 @@ nsresult PeerConnectionMedia::Init(const
                 WrapRunnable(mIceCtx, &NrIceCtx::StartGathering), NS_DISPATCH_NORMAL);
 
   return NS_OK;
 }
 
 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;
   }
 
   DOMMediaStream* stream = static_cast<DOMMediaStream*>(aMediaStream);
 
   CSFLogDebug(logTag, "%s: MediaStream: %p",
@@ -258,17 +260,16 @@ PeerConnectionMedia::AddStream(nsIDOMMed
     CSFLogDebug(logTag, "Empty Stream !!");
     return NS_OK;
   }
 
   // Now see if we already have a stream of this type, since we only
   // 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<LocalSourceStreamInfo> localSourceStream = mLocalSourceStreams[u];
 
     if (localSourceStream->GetMediaStream()->GetHintContents() & hints) {
       CSFLogError(logTag, "Only one stream of any given type allowed");
       return NS_ERROR_FAILURE;
     }
   }
@@ -290,23 +291,23 @@ PeerConnectionMedia::AddStream(nsIDOMMed
 
   return NS_OK;
 }
 
 nsresult
 PeerConnectionMedia::RemoveStream(nsIDOMMediaStream* aMediaStream, uint32_t *stream_id)
 {
   MOZ_ASSERT(aMediaStream);
+  ASSERT_ON_THREAD(mMainThread);
 
   DOMMediaStream* stream = static_cast<DOMMediaStream*>(aMediaStream);
 
   CSFLogDebug(logTag, "%s: MediaStream: %p",
     __FUNCTION__, aMediaStream);
 
-  mozilla::MutexAutoLock lock(mLocalSourceStreamsLock);
   for (uint32_t u = 0; u < mLocalSourceStreams.Length(); u++) {
     nsRefPtr<LocalSourceStreamInfo> localSourceStream = mLocalSourceStreams[u];
     if (localSourceStream->GetMediaStream() == stream) {
       *stream_id = u;
       return NS_OK;
     }
   }
 
@@ -352,16 +353,21 @@ PeerConnectionMedia::SelfDestruct_m()
 
 void
 PeerConnectionMedia::ShutdownMediaTransport_s()
 {
   ASSERT_ON_THREAD(mSTSThread);
 
   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();
   }
 
   for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) {
     mRemoteSourceStreams[i]->DetachTransport_s();
   }
 
@@ -372,27 +378,29 @@ PeerConnectionMedia::ShutdownMediaTransp
 
   mMainThread->Dispatch(WrapRunnable(this, &PeerConnectionMedia::SelfDestruct_m),
                         NS_DISPATCH_NORMAL);
 }
 
 LocalSourceStreamInfo*
 PeerConnectionMedia::GetLocalStream(int aIndex)
 {
+  ASSERT_ON_THREAD(mMainThread);
   if(aIndex < 0 || aIndex >= (int) mLocalSourceStreams.Length()) {
     return nullptr;
   }
 
   MOZ_ASSERT(mLocalSourceStreams[aIndex]);
   return mLocalSourceStreams[aIndex];
 }
 
 RemoteSourceStreamInfo*
 PeerConnectionMedia::GetRemoteStream(int aIndex)
 {
+  ASSERT_ON_THREAD(mMainThread);
   if(aIndex < 0 || aIndex >= (int) mRemoteSourceStreams.Length()) {
     return nullptr;
   }
 
   MOZ_ASSERT(mRemoteSourceStreams[aIndex]);
   return mRemoteSourceStreams[aIndex];
 }
 
@@ -467,16 +475,17 @@ PeerConnectionMedia::UpdateFilterFromRem
   }
   return false;
 }
 
 nsresult
 PeerConnectionMedia::AddRemoteStream(nsRefPtr<RemoteSourceStreamInfo> aInfo,
   int *aIndex)
 {
+  ASSERT_ON_THREAD(mMainThread);
   MOZ_ASSERT(aIndex);
 
   *aIndex = mRemoteSourceStreams.Length();
 
   mRemoteSourceStreams.AppendElement(aInfo);
 
   return NS_OK;
 }
@@ -519,34 +528,138 @@ PeerConnectionMedia::IceConnectionStateC
 void
 PeerConnectionMedia::IceStreamReady(NrIceMediaStream *aStream)
 {
   MOZ_ASSERT(aStream);
 
   CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str());
 }
 
+
 void
-LocalSourceStreamInfo::StorePipeline(int aTrack,
-  mozilla::RefPtr<mozilla::MediaPipeline> 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<TransportFlow> &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<TransportFlow>& 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<MediaPipelineTransmit*>((*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<mozilla::MediaPipelineTransmit> aPipeline)
 {
   MOZ_ASSERT(mPipelines.find(aTrack) == mPipelines.end());
   if (mPipelines.find(aTrack) != mPipelines.end()) {
     CSFLogError(logTag, "%s: Storing duplicate track", __FUNCTION__);
     return;
   }
   //TODO: Revisit once we start supporting multiple streams or multiple tracks
   // of same type
   mPipelines[aTrack] = aPipeline;
 }
 
 void
-RemoteSourceStreamInfo::StorePipeline(int aTrack,
-                                      bool aIsVideo,
-                                      mozilla::RefPtr<mozilla::MediaPipeline> aPipeline)
+RemoteSourceStreamInfo::StorePipeline(
+  int aTrack, bool aIsVideo,
+  mozilla::RefPtr<mozilla::MediaPipelineReceive> aPipeline)
 {
   MOZ_ASSERT(mPipelines.find(aTrack) == mPipelines.end());
   if (mPipelines.find(aTrack) != mPipelines.end()) {
     CSFLogError(logTag, "%s: Request to store duplicate track %d", __FUNCTION__, aTrack);
     return;
   }
   CSFLogDebug(logTag, "%s track %d %s = %p", __FUNCTION__, aTrack, aIsVideo ? "video" : "audio",
               aPipeline.get());
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -26,18 +26,21 @@
 
 #ifdef MOZILLA_INTERNAL_API
 #include "Layers.h"
 #include "VideoUtils.h"
 #include "ImageLayers.h"
 #include "VideoSegment.h"
 #endif
 
+class nsIPrincipal;
+
 namespace mozilla {
 class DataChannel;
+class PeerIdentity;
 namespace dom {
 class RTCInboundRTPStreamStats;
 class RTCOutboundRTPStreamStats;
 }
 }
 
 #include "nricectx.h"
 #include "nriceresolver.h"
@@ -175,17 +178,17 @@ public:
   SourceStreamInfo(DOMMediaStream* aMediaStream,
                    PeerConnectionMedia *aParent)
       : mMediaStream(aMediaStream),
         mParent(aParent) {
     MOZ_ASSERT(mMediaStream);
   }
 
   SourceStreamInfo(already_AddRefed<DOMMediaStream>& aMediaStream,
-                  PeerConnectionMedia *aParent)
+                   PeerConnectionMedia *aParent)
       : mMediaStream(aMediaStream),
         mParent(aParent) {
     MOZ_ASSERT(mMediaStream);
   }
 
   // This method exists for stats and the unittests.
   // It allows visibility into the pipelines and flows.
   const std::map<mozilla::TrackID, mozilla::RefPtr<mozilla::MediaPipeline>>&
@@ -210,17 +213,22 @@ public:
 
   ~LocalSourceStreamInfo() {
     mMediaStream = nullptr;
   }
 
   DOMMediaStream* GetMediaStream() {
     return mMediaStream;
   }
-  void StorePipeline(int aTrack, mozilla::RefPtr<mozilla::MediaPipeline> aPipeline);
+  void StorePipeline(int aTrack,
+                     mozilla::RefPtr<mozilla::MediaPipelineTransmit> aPipeline);
+
+#ifdef MOZILLA_INTERNAL_API
+  void UpdateSinkIdentity_m(nsIPrincipal* aPrincipal, const PeerIdentity* aSinkIdentity);
+#endif
 
   void ExpectAudio(const mozilla::TrackID);
   void ExpectVideo(const mozilla::TrackID);
   unsigned AudioTrackCount();
   unsigned VideoTrackCount();
   void DetachTransport_s();
   void DetachMedia_m();
 
@@ -238,23 +246,27 @@ class RemoteSourceStreamInfo : public So
                          PeerConnectionMedia *aParent)
     : SourceStreamInfo(aMediaStream, aParent),
       mTrackTypeHints(0) {}
 
   DOMMediaStream* GetMediaStream() {
     return mMediaStream;
   }
   void StorePipeline(int aTrack, bool aIsVideo,
-                     mozilla::RefPtr<mozilla::MediaPipeline> aPipeline);
+                     mozilla::RefPtr<mozilla::MediaPipelineReceive> 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:
   DOMMediaStream::TrackTypeHints mTrackTypeHints;
  private:
   std::map<int, bool> mTypes;
 };
 
@@ -278,20 +290,20 @@ class PeerConnectionMedia : public sigsl
     }
     return mIceStreams[i];
   }
 
   size_t num_ice_media_streams() const {
     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
   uint32_t LocalStreamsLength()
   {
     return mLocalSourceStreams.Length();
   }
   LocalSourceStreamInfo* GetLocalStream(int index);
@@ -307,16 +319,29 @@ class PeerConnectionMedia : public sigsl
   bool UpdateFilterFromRemoteDescription_m(
       int level,
       nsAutoPtr<mozilla::MediaPipelineFilter> filter);
 
   // Add a remote stream. Returns the index in index
   nsresult AddRemoteStream(nsRefPtr<RemoteSourceStreamInfo> 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<nsIThread>& GetMainThread() const { return mMainThread; }
   const nsCOMPtr<nsIEventTarget>& GetSTSThread() const { return mSTSThread; }
 
   // Get a transport flow either RTP/RTCP for a particular stream
   // A stream can be of audio/video/datachannel/budled(?) types
   mozilla::RefPtr<mozilla::TransportFlow> GetTransportFlow(int aStreamIndex,
                                                            bool aIsRtcp) {
     int index_inner = aStreamIndex * 2 + (aIsRtcp ? 1 : 0);
@@ -324,22 +349,20 @@ class PeerConnectionMedia : public sigsl
     if (mTransportFlows.find(index_inner) == mTransportFlows.end())
       return nullptr;
 
     return mTransportFlows[index_inner];
   }
 
   // Add a transport flow
   void AddTransportFlow(int aIndex, bool aRtcp,
-                        const mozilla::RefPtr<mozilla::TransportFlow> &aFlow) {
-    int index_inner = aIndex * 2 + (aRtcp ? 1 : 0);
-
-    MOZ_ASSERT(!mTransportFlows[index_inner]);
-    mTransportFlows[index_inner] = aFlow;
-  }
+                        const mozilla::RefPtr<mozilla::TransportFlow> &aFlow);
+  void ConnectDtlsListener_s(const mozilla::RefPtr<mozilla::TransportFlow>& aFlow);
+  void DtlsConnected(mozilla::TransportLayer* aFlow,
+                     mozilla::TransportLayer::State state);
 
   mozilla::RefPtr<mozilla::MediaSessionConduit> GetConduit(int aStreamIndex, bool aReceive) {
     int index_inner = aStreamIndex * 2 + (aReceive ? 0 : 1);
 
     if (mConduits.find(index_inner) == mConduits.end())
       return nullptr;
 
     return mConduits[index_inner];
@@ -374,20 +397,21 @@ class PeerConnectionMedia : public sigsl
   void IceConnectionStateChange(mozilla::NrIceCtx* ctx,
                                 mozilla::NrIceCtx::ConnectionState state);
   void IceStreamReady(mozilla::NrIceMediaStream *aStream);
 
   // The parent PC
   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<nsRefPtr<LocalSourceStreamInfo> > mLocalSourceStreams;
 
   // A list of streams provided by the other side
+  // This is only accessed on the main thread (with one special exception)
   nsTArray<nsRefPtr<RemoteSourceStreamInfo> > mRemoteSourceStreams;
 
   // ICE objects
   mozilla::RefPtr<mozilla::NrIceCtx> mIceCtx;
   std::vector<mozilla::RefPtr<mozilla::NrIceMediaStream> > mIceStreams;
 
   // DNS
   nsRefPtr<mozilla::NrIceResolver> mDNSResolver;
--- a/media/webrtc/signaling/test/FakeMediaStreams.h
+++ b/media/webrtc/signaling/test/FakeMediaStreams.h
@@ -85,17 +85,17 @@ class Fake_MediaStream {
 
   virtual void Periodic() {}
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStream);
 
  protected:
   std::set<Fake_MediaStreamListener *> mListeners;
   mozilla::Mutex mMutex;  // Lock to prevent the listener list from being modified while
-  		 	  // executing Periodic().
+                          // executing Periodic().
 };
 
 class Fake_MediaPeriodic : public nsITimerCallback {
 public:
 Fake_MediaPeriodic(Fake_MediaStream *aStream) : mStream(aStream),
                                                 mCount(0) {}
   virtual ~Fake_MediaPeriodic() {}
   void Detach() {
@@ -237,16 +237,18 @@ public:
   typedef uint8_t TrackTypeHints;
   enum {
     HINT_CONTENTS_AUDIO = 0x01,
     HINT_CONTENTS_VIDEO = 0x02
   };
   uint32_t GetHintContents() const { return mHintContents; }
   void SetHintContents(uint32_t aHintContents) { mHintContents = aHintContents; }
 
+  void SetTrackEnabled(mozilla::TrackID aTrackID, bool aEnabled) {}
+
 private:
   nsRefPtr<Fake_MediaStream> mMediaStream;
 
   // tells the SDP generator about whether this
   // MediaStream probably has audio and/or video
   uint32_t mHintContents;
 };