summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/jsapi
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/media/webrtc/jsapi
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandler.cpp1593
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandler.h163
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp398
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h94
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportParent.cpp238
-rw-r--r--dom/media/webrtc/jsapi/PacketDumper.cpp51
-rw-r--r--dom/media/webrtc/jsapi/PacketDumper.h33
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionCtx.cpp409
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionCtx.h118
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionImpl.cpp3019
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionImpl.h642
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionMedia.cpp891
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionMedia.h262
-rw-r--r--dom/media/webrtc/jsapi/RTCDTMFSender.cpp150
-rw-r--r--dom/media/webrtc/jsapi/RTCDTMFSender.h58
-rw-r--r--dom/media/webrtc/jsapi/RTCDtlsTransport.cpp69
-rw-r--r--dom/media/webrtc/jsapi/RTCDtlsTransport.h45
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpReceiver.cpp619
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpReceiver.h137
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp34
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsIdGenerator.h34
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsReport.cpp94
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsReport.h119
-rw-r--r--dom/media/webrtc/jsapi/RemoteTrackSource.h59
-rw-r--r--dom/media/webrtc/jsapi/TransceiverImpl.cpp900
-rw-r--r--dom/media/webrtc/jsapi/TransceiverImpl.h185
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalChild.h42
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp714
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalInformation.h57
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalParent.h43
-rw-r--r--dom/media/webrtc/jsapi/moz.build37
31 files changed, 11307 insertions, 0 deletions
diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.cpp b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp
new file mode 100644
index 0000000000..1360380b87
--- /dev/null
+++ b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp
@@ -0,0 +1,1593 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "MediaTransportHandler.h"
+#include "MediaTransportHandlerIPC.h"
+#include "transport/nricemediastream.h"
+#include "transport/nriceresolver.h"
+#include "transport/transportflow.h"
+#include "transport/transportlayerice.h"
+#include "transport/transportlayerdtls.h"
+#include "transport/transportlayersrtp.h"
+
+// Config stuff
+#include "mozilla/dom/RTCConfigurationBinding.h"
+#include "mozilla/Preferences.h"
+
+// Parsing STUN/TURN URIs
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsIURLParser.h"
+
+// Logging stuff
+#include "common/browser_logging/CSFLog.h"
+
+// For fetching ICE logging
+#include "transport/rlogconnector.h"
+
+// DTLS
+#include "sdp/SdpAttribute.h"
+
+#include "transport/runnable_utils.h"
+
+#include "mozilla/Algorithm.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/dom/RTCStatsReportBinding.h"
+
+#include "nss.h" // For NSS_NoDB_Init
+#include "mozilla/PublicSSL.h" // For psm::InitializeCipherSuite
+
+#include "nsISocketTransportService.h"
+
+#include <string>
+#include <vector>
+#include <map>
+
+namespace mozilla {
+
+static const char* mthLogTag = "MediaTransportHandler";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG mthLogTag
+
+class MediaTransportHandlerSTS : public MediaTransportHandler,
+ public sigslot::has_slots<> {
+ public:
+ explicit MediaTransportHandlerSTS(nsISerialEventTarget* aCallbackThread);
+
+ RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) override;
+ void ClearIceLog() override;
+ void EnterPrivateMode() override;
+ void ExitPrivateMode() override;
+
+ nsresult CreateIceCtx(const std::string& aName,
+ const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) override;
+
+ // We will probably be able to move the proxy lookup stuff into
+ // this class once we move mtransport to its own process.
+ void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override;
+
+ void EnsureProvisionalTransport(const std::string& aTransportId,
+ const std::string& aUfrag,
+ const std::string& aPwd,
+ size_t aComponentCount) override;
+
+ void SetTargetForDefaultLocalAddressLookup(const std::string& aTargetIp,
+ uint16_t aTargetPort) override;
+
+ // We set default-route-only as late as possible because it depends on what
+ // capture permissions have been granted on the window, which could easily
+ // change between Init (ie; when the PC is created) and StartIceGathering
+ // (ie; when we set the local description).
+ void StartIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses,
+ // This will go away once mtransport moves to its
+ // own process, because we won't need to get this
+ // via IPC anymore
+ const nsTArray<NrIceStunAddr>& aStunAddrs) override;
+
+ void ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) override;
+
+ void RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) override;
+
+ void StartIceChecks(bool aIsControlling,
+ const std::vector<std::string>& aIceOptions) override;
+
+ void AddIceCandidate(const std::string& aTransportId,
+ const std::string& aCandidate, const std::string& aUfrag,
+ const std::string& aObfuscatedAddress) override;
+
+ void UpdateNetworkState(bool aOnline) override;
+
+ void SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) override;
+
+ RefPtr<dom::RTCStatsPromise> GetIceStats(const std::string& aTransportId,
+ DOMHighResTimeStamp aNow) override;
+
+ void Shutdown();
+
+ private:
+ void Destroy() override;
+ void Destroy_s();
+ void DestroyFinal();
+ void Shutdown_s();
+ RefPtr<TransportFlow> CreateTransportFlow(const std::string& aTransportId,
+ bool aIsRtcp,
+ RefPtr<DtlsIdentity> aDtlsIdentity,
+ bool aDtlsClient,
+ const DtlsDigestList& aDigests,
+ bool aPrivacyRequested);
+
+ struct Transport {
+ RefPtr<TransportFlow> mFlow;
+ RefPtr<TransportFlow> mRtcpFlow;
+ };
+
+ using MediaTransportHandler::OnAlpnNegotiated;
+ using MediaTransportHandler::OnCandidate;
+ using MediaTransportHandler::OnConnectionStateChange;
+ using MediaTransportHandler::OnEncryptedSending;
+ using MediaTransportHandler::OnGatheringStateChange;
+ using MediaTransportHandler::OnPacketReceived;
+ using MediaTransportHandler::OnRtcpStateChange;
+ using MediaTransportHandler::OnStateChange;
+
+ void OnGatheringStateChange(NrIceCtx* aIceCtx,
+ NrIceCtx::GatheringState aState);
+ void OnConnectionStateChange(NrIceCtx* aIceCtx,
+ NrIceCtx::ConnectionState aState);
+ void OnCandidateFound(NrIceMediaStream* aStream,
+ const std::string& aCandidate,
+ const std::string& aUfrag, const std::string& aMDNSAddr,
+ const std::string& aActualAddr);
+ void OnStateChange(TransportLayer* aLayer, TransportLayer::State);
+ void OnRtcpStateChange(TransportLayer* aLayer, TransportLayer::State);
+ void PacketReceived(TransportLayer* aLayer, MediaPacket& aPacket);
+ void EncryptedPacketSending(TransportLayer* aLayer, MediaPacket& aPacket);
+ RefPtr<TransportFlow> GetTransportFlow(const std::string& aTransportId,
+ bool aIsRtcp) const;
+ void GetIceStats(const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow,
+ dom::RTCStatsCollection* aStats) const;
+
+ virtual ~MediaTransportHandlerSTS() = default;
+ nsCOMPtr<nsISerialEventTarget> mStsThread;
+ RefPtr<NrIceCtx> mIceCtx;
+ RefPtr<NrIceResolver> mDNSResolver;
+ std::map<std::string, Transport> mTransports;
+ bool mObfuscateHostAddresses = false;
+ uint32_t mMinDtlsVersion = 0;
+ uint32_t mMaxDtlsVersion = 0;
+
+ std::set<std::string> mSignaledAddresses;
+
+ // Init can only be done on main, but we want this to be usable on any thread
+ typedef MozPromise<bool, std::string, false> InitPromise;
+ RefPtr<InitPromise> mInitPromise;
+};
+
+/* static */
+already_AddRefed<MediaTransportHandler> MediaTransportHandler::Create(
+ nsISerialEventTarget* aCallbackThread) {
+ RefPtr<MediaTransportHandler> result;
+ if (XRE_IsContentProcess() &&
+ Preferences::GetBool("media.peerconnection.mtransport_process") &&
+ Preferences::GetBool("network.process.enabled")) {
+ result = new MediaTransportHandlerIPC(aCallbackThread);
+ } else {
+ result = new MediaTransportHandlerSTS(aCallbackThread);
+ }
+ return result.forget();
+}
+
+class STSShutdownHandler : public nsISTSShutdownObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // Lazy singleton
+ static RefPtr<STSShutdownHandler>& Instance() {
+ MOZ_ASSERT(NS_IsMainThread());
+ static RefPtr<STSShutdownHandler> sHandler(new STSShutdownHandler);
+ return sHandler;
+ }
+
+ void Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ for (const auto& handler : mHandlers) {
+ handler->Shutdown();
+ }
+ mHandlers.clear();
+ }
+
+ STSShutdownHandler() {
+ CSFLogDebug(LOGTAG, "%s", __func__);
+ nsresult res;
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(res));
+ MOZ_RELEASE_ASSERT(sts);
+ sts->AddShutdownObserver(this);
+ }
+
+ NS_IMETHOD Observe() override {
+ CSFLogDebug(LOGTAG, "%s", __func__);
+ Shutdown();
+ nsresult res;
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(res));
+ MOZ_RELEASE_ASSERT(sts);
+ sts->RemoveShutdownObserver(this);
+ Instance() = nullptr;
+ return NS_OK;
+ }
+
+ void Register(MediaTransportHandlerSTS* aHandler) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mHandlers.insert(aHandler);
+ }
+
+ void Deregister(MediaTransportHandlerSTS* aHandler) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mHandlers.erase(aHandler);
+ }
+
+ private:
+ virtual ~STSShutdownHandler() {}
+
+ // Raw ptrs, registered on init, deregistered on destruction, all on main
+ std::set<MediaTransportHandlerSTS*> mHandlers;
+};
+
+NS_IMPL_ISUPPORTS(STSShutdownHandler, nsISTSShutdownObserver);
+
+MediaTransportHandlerSTS::MediaTransportHandlerSTS(
+ nsISerialEventTarget* aCallbackThread)
+ : MediaTransportHandler(aCallbackThread) {
+ nsresult rv;
+ mStsThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (!mStsThread) {
+ MOZ_CRASH();
+ }
+
+ RLogConnector::CreateInstance();
+
+ CSFLogDebug(LOGTAG, "%s done %p", __func__, this);
+
+ // We do not set up mDNSService here, because we are not running on main (we
+ // use PBackground), and the DNS service asserts.
+}
+
+static NrIceCtx::Policy toNrIcePolicy(dom::RTCIceTransportPolicy aPolicy) {
+ switch (aPolicy) {
+ case dom::RTCIceTransportPolicy::Relay:
+ return NrIceCtx::ICE_POLICY_RELAY;
+ case dom::RTCIceTransportPolicy::All:
+ if (Preferences::GetBool("media.peerconnection.ice.no_host", false)) {
+ return NrIceCtx::ICE_POLICY_NO_HOST;
+ } else {
+ return NrIceCtx::ICE_POLICY_ALL;
+ }
+ default:
+ MOZ_CRASH();
+ }
+ return NrIceCtx::ICE_POLICY_ALL;
+}
+
+static nsresult addNrIceServer(const nsString& aIceUrl,
+ const dom::RTCIceServer& aIceServer,
+ std::vector<NrIceStunServer>* aStunServersOut,
+ std::vector<NrIceTurnServer>* aTurnServersOut) {
+ // Without STUN/TURN handlers, NS_NewURI returns nsSimpleURI rather than
+ // nsStandardURL. To parse STUN/TURN URI's to spec
+ // http://tools.ietf.org/html/draft-nandakumar-rtcweb-stun-uri-02#section-3
+ // http://tools.ietf.org/html/draft-petithuguenin-behave-turn-uri-03#section-3
+ // we parse out the query-string, and use ParseAuthority() on the rest
+ RefPtr<nsIURI> url;
+ nsresult rv = NS_NewURI(getter_AddRefs(url), aIceUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isStun = url->SchemeIs("stun");
+ bool isStuns = url->SchemeIs("stuns");
+ bool isTurn = url->SchemeIs("turn");
+ bool isTurns = url->SchemeIs("turns");
+ if (!(isStun || isStuns || isTurn || isTurns)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (isStuns) {
+ return NS_OK; // TODO: Support STUNS (Bug 1056934)
+ }
+
+ nsAutoCString spec;
+ rv = url->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // TODO(jib@mozilla.com): Revisit once nsURI supports STUN/TURN (Bug 833509)
+ int32_t port;
+ nsAutoCString host;
+ nsAutoCString transport;
+ {
+ uint32_t hostPos;
+ int32_t hostLen;
+ nsAutoCString path;
+ rv = url->GetPathQueryRef(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Tolerate query-string + parse 'transport=[udp|tcp]' by hand.
+ int32_t questionmark = path.FindChar('?');
+ if (questionmark >= 0) {
+ const nsCString match = "transport="_ns;
+
+ for (int32_t i = questionmark, endPos; i >= 0; i = endPos) {
+ endPos = path.FindCharInSet("&", i + 1);
+ const nsDependentCSubstring fieldvaluepair =
+ Substring(path, i + 1, endPos);
+ if (StringBeginsWith(fieldvaluepair, match)) {
+ transport = Substring(fieldvaluepair, match.Length());
+ ToLowerCase(transport);
+ }
+ }
+ path.SetLength(questionmark);
+ }
+
+ rv = net_GetAuthURLParser()->ParseAuthority(
+ path.get(), path.Length(), nullptr, nullptr, nullptr, nullptr, &hostPos,
+ &hostLen, &port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hostLen) {
+ return NS_ERROR_FAILURE;
+ }
+ if (hostPos > 1) /* The username was removed */
+ return NS_ERROR_FAILURE;
+ path.Mid(host, hostPos, hostLen);
+ }
+ if (port == -1) port = (isStuns || isTurns) ? 5349 : 3478;
+
+ if (isStuns || isTurns) {
+ // Should we barf if transport is set to udp or something?
+ transport = kNrIceTransportTls;
+ }
+
+ if (transport.IsEmpty()) {
+ transport = kNrIceTransportUdp;
+ }
+
+ if (isTurn || isTurns) {
+ std::string pwd(
+ NS_ConvertUTF16toUTF8(aIceServer.mCredential.Value()).get());
+ std::string username(
+ NS_ConvertUTF16toUTF8(aIceServer.mUsername.Value()).get());
+
+ std::vector<unsigned char> password(pwd.begin(), pwd.end());
+
+ UniquePtr<NrIceTurnServer> server(NrIceTurnServer::Create(
+ host.get(), port, username, password, transport.get()));
+ if (!server) {
+ return NS_ERROR_FAILURE;
+ }
+ aTurnServersOut->emplace_back(std::move(*server));
+ } else {
+ UniquePtr<NrIceStunServer> server(
+ NrIceStunServer::Create(host.get(), port, transport.get()));
+ if (!server) {
+ return NS_ERROR_FAILURE;
+ }
+ aStunServersOut->emplace_back(std::move(*server));
+ }
+ return NS_OK;
+}
+
+/* static */
+nsresult MediaTransportHandler::ConvertIceServers(
+ const nsTArray<dom::RTCIceServer>& aIceServers,
+ std::vector<NrIceStunServer>* aStunServers,
+ std::vector<NrIceTurnServer>* aTurnServers) {
+ for (const auto& iceServer : aIceServers) {
+ NS_ENSURE_STATE(iceServer.mUrls.WasPassed());
+ NS_ENSURE_STATE(iceServer.mUrls.Value().IsStringSequence());
+ for (const auto& iceUrl : iceServer.mUrls.Value().GetAsStringSequence()) {
+ nsresult rv =
+ addNrIceServer(iceUrl, iceServer, aStunServers, aTurnServers);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: invalid STUN/TURN server: %s", __FUNCTION__,
+ NS_ConvertUTF16toUTF8(iceUrl).get());
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+static NrIceCtx::GlobalConfig GetGlobalConfig() {
+ NrIceCtx::GlobalConfig config;
+ config.mAllowLinkLocal =
+ Preferences::GetBool("media.peerconnection.ice.link_local", false);
+ config.mAllowLoopback =
+ Preferences::GetBool("media.peerconnection.ice.loopback", false);
+ config.mTcpEnabled =
+ Preferences::GetBool("media.peerconnection.ice.tcp", false);
+ config.mStunClientMaxTransmits = Preferences::GetInt(
+ "media.peerconnection.ice.stun_client_maximum_transmits",
+ config.mStunClientMaxTransmits);
+ config.mTrickleIceGracePeriod =
+ Preferences::GetInt("media.peerconnection.ice.trickle_grace_period",
+ config.mTrickleIceGracePeriod);
+ config.mIceTcpSoSockCount = Preferences::GetInt(
+ "media.peerconnection.ice.tcp_so_sock_count", config.mIceTcpSoSockCount);
+ config.mIceTcpListenBacklog =
+ Preferences::GetInt("media.peerconnection.ice.tcp_listen_backlog",
+ config.mIceTcpListenBacklog);
+ (void)Preferences::GetCString("media.peerconnection.ice.force_interface",
+ config.mForceNetInterface);
+ return config;
+}
+
+static Maybe<NrIceCtx::NatSimulatorConfig> GetNatConfig() {
+ bool block_tcp = Preferences::GetBool(
+ "media.peerconnection.nat_simulator.block_tcp", false);
+ bool block_udp = Preferences::GetBool(
+ "media.peerconnection.nat_simulator.block_udp", false);
+ int error_code_for_drop = Preferences::GetInt(
+ "media.peerconnection.nat_simulator.error_code_for_drop", 0);
+ nsAutoCString mapping_type;
+ (void)Preferences::GetCString(
+ "media.peerconnection.nat_simulator.mapping_type", mapping_type);
+ nsAutoCString filtering_type;
+ (void)Preferences::GetCString(
+ "media.peerconnection.nat_simulator.filtering_type", filtering_type);
+
+ if (block_udp || block_tcp || !mapping_type.IsEmpty() ||
+ !filtering_type.IsEmpty()) {
+ CSFLogDebug(LOGTAG, "NAT filtering type: %s", filtering_type.get());
+ CSFLogDebug(LOGTAG, "NAT mapping type: %s", mapping_type.get());
+ NrIceCtx::NatSimulatorConfig natConfig;
+ natConfig.mBlockUdp = block_udp;
+ natConfig.mBlockTcp = block_tcp;
+ natConfig.mErrorCodeForDrop = error_code_for_drop;
+ natConfig.mFilteringType = filtering_type;
+ natConfig.mMappingType = mapping_type;
+ return Some(natConfig);
+ }
+ return Nothing();
+}
+
+nsresult MediaTransportHandlerSTS::CreateIceCtx(
+ const std::string& aName, const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) {
+ // We rely on getting an error when this happens, so do it up front.
+ std::vector<NrIceStunServer> stunServers;
+ std::vector<NrIceTurnServer> turnServers;
+ nsresult rv = ConvertIceServers(aIceServers, &stunServers, &turnServers);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mInitPromise = InvokeAsync(
+ GetMainThreadSerialEventTarget(), __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ CSFLogDebug(LOGTAG, "%s starting", __func__);
+ if (!NSS_IsInitialized()) {
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ MOZ_CRASH();
+ return InitPromise::CreateAndReject("NSS_NoDB_Init failed",
+ __func__);
+ }
+
+ if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) {
+ MOZ_CRASH();
+ return InitPromise::CreateAndReject("InitializeCipherSuite failed",
+ __func__);
+ }
+
+ mozilla::psm::DisableMD5();
+ }
+
+ static bool globalInitDone = false;
+ if (!globalInitDone) {
+ mStsThread->Dispatch(
+ WrapRunnableNM(&NrIceCtx::InitializeGlobals, GetGlobalConfig()),
+ NS_DISPATCH_NORMAL);
+ globalInitDone = true;
+ }
+
+ // Give us a way to globally turn off TURN support
+ bool turnDisabled =
+ Preferences::GetBool("media.peerconnection.turn.disable", false);
+ // We are reading these here, because when we setup the DTLS transport
+ // we are on the wrong thread to read prefs
+ mMinDtlsVersion =
+ Preferences::GetUint("media.peerconnection.dtls.version.min");
+ mMaxDtlsVersion =
+ Preferences::GetUint("media.peerconnection.dtls.version.max");
+
+ NrIceCtx::Config config;
+ config.mPolicy = toNrIcePolicy(aIcePolicy);
+ config.mNatSimulatorConfig = GetNatConfig();
+
+ MOZ_RELEASE_ASSERT(STSShutdownHandler::Instance());
+ STSShutdownHandler::Instance()->Register(this);
+
+ return InvokeAsync(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ mIceCtx = NrIceCtx::Create(aName, config);
+ if (!mIceCtx) {
+ return InitPromise::CreateAndReject("NrIceCtx::Create failed",
+ __func__);
+ }
+
+ mIceCtx->SignalGatheringStateChange.connect(
+ this, &MediaTransportHandlerSTS::OnGatheringStateChange);
+ mIceCtx->SignalConnectionStateChange.connect(
+ this, &MediaTransportHandlerSTS::OnConnectionStateChange);
+
+ nsresult rv;
+
+ if (NS_FAILED(rv = mIceCtx->SetStunServers(stunServers))) {
+ CSFLogError(LOGTAG, "%s: Failed to set stun servers",
+ __FUNCTION__);
+ return InitPromise::CreateAndReject(
+ "Failed to set stun servers", __func__);
+ }
+ if (!turnDisabled) {
+ if (NS_FAILED(rv = mIceCtx->SetTurnServers(turnServers))) {
+ CSFLogError(LOGTAG, "%s: Failed to set turn servers",
+ __FUNCTION__);
+ return InitPromise::CreateAndReject(
+ "Failed to set turn servers", __func__);
+ }
+ } else if (!turnServers.empty()) {
+ CSFLogError(LOGTAG, "%s: Setting turn servers disabled",
+ __FUNCTION__);
+ }
+
+ mDNSResolver = new NrIceResolver;
+ if (NS_FAILED(rv = mDNSResolver->Init())) {
+ CSFLogError(LOGTAG, "%s: Failed to initialize dns resolver",
+ __FUNCTION__);
+ return InitPromise::CreateAndReject(
+ "Failed to initialize dns resolver", __func__);
+ }
+ if (NS_FAILED(rv = mIceCtx->SetResolver(
+ mDNSResolver->AllocateResolver()))) {
+ CSFLogError(LOGTAG, "%s: Failed to get dns resolver",
+ __FUNCTION__);
+ return InitPromise::CreateAndReject(
+ "Failed to get dns resolver", __func__);
+ }
+
+ CSFLogDebug(LOGTAG, "%s done", __func__);
+ return InitPromise::CreateAndResolve(true, __func__);
+ });
+ });
+ return NS_OK;
+}
+
+void MediaTransportHandlerSTS::Shutdown() {
+ CSFLogDebug(LOGTAG, "%s", __func__);
+ MOZ_ASSERT(NS_IsMainThread());
+ mStsThread->Dispatch(NewNonOwningRunnableMethod(
+ __func__, this, &MediaTransportHandlerSTS::Shutdown_s));
+}
+
+void MediaTransportHandlerSTS::Shutdown_s() {
+ CSFLogDebug(LOGTAG, "%s", __func__);
+ disconnect_all();
+ // Clear the transports before destroying the ice ctx so that
+ // the close_notify alerts have a chance to be sent as the
+ // TransportFlow destructors execute.
+ mTransports.clear();
+ if (mIceCtx) {
+ NrIceStats stats = mIceCtx->Destroy();
+ CSFLogDebug(LOGTAG,
+ "Ice Telemetry: stun (retransmits: %d)"
+ " turn (401s: %d 403s: %d 438s: %d)",
+ stats.stun_retransmits, stats.turn_401s, stats.turn_403s,
+ stats.turn_438s);
+ }
+ mIceCtx = nullptr;
+ mDNSResolver = nullptr;
+}
+
+void MediaTransportHandlerSTS::Destroy() {
+ CSFLogDebug(LOGTAG, "%s %p", __func__, this);
+ // Our "destruction tour" starts on main, because we need to deregister.
+ if (!NS_IsMainThread()) {
+ GetMainThreadEventTarget()->Dispatch(NewNonOwningRunnableMethod(
+ __func__, this, &MediaTransportHandlerSTS::Destroy));
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!STSShutdownHandler::Instance()) {
+ CSFLogDebug(LOGTAG, "%s Already shut down. Nothing else to do.", __func__);
+ delete this;
+ return;
+ }
+
+ STSShutdownHandler::Instance()->Deregister(this);
+ Shutdown();
+
+ // mIceCtx still has a reference to us via sigslot! We must dispach to STS,
+ // and clean up there. However, by the time _that_ happens, we may have
+ // dispatched a signal callback to mCallbackThread, so we have to dispatch
+ // the final destruction to mCallbackThread.
+ nsresult rv = mStsThread->Dispatch(NewNonOwningRunnableMethod(
+ __func__, this, &MediaTransportHandlerSTS::Destroy_s));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CSFLogError(LOGTAG,
+ "Unable to dispatch to STS: why has the XPCOM shutdown handler "
+ "not been invoked?");
+ delete this;
+ }
+}
+
+void MediaTransportHandlerSTS::Destroy_s() {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ nsresult rv = mCallbackThread->Dispatch(NewNonOwningRunnableMethod(
+ __func__, this, &MediaTransportHandlerSTS::DestroyFinal));
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+ }
+
+ DestroyFinal();
+}
+
+void MediaTransportHandlerSTS::DestroyFinal() { delete this; }
+
+void MediaTransportHandlerSTS::SetProxyConfig(
+ NrSocketProxyConfig&& aProxyConfig) {
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [this, self = RefPtr<MediaTransportHandlerSTS>(this),
+ aProxyConfig = std::move(aProxyConfig)]() mutable {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ mIceCtx->SetProxyConfig(std::move(aProxyConfig));
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::EnsureProvisionalTransport(
+ const std::string& aTransportId, const std::string& aUfrag,
+ const std::string& aPwd, size_t aComponentCount) {
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
+ if (!stream) {
+ CSFLogDebug(LOGTAG, "%s: Creating ICE media stream=%s components=%u",
+ mIceCtx->name().c_str(), aTransportId.c_str(),
+ static_cast<unsigned>(aComponentCount));
+
+ std::ostringstream os;
+ os << mIceCtx->name() << " transport-id=" << aTransportId;
+ stream =
+ mIceCtx->CreateStream(aTransportId, os.str(), aComponentCount);
+
+ if (!stream) {
+ CSFLogError(LOGTAG, "Failed to create ICE stream.");
+ return;
+ }
+
+ stream->SignalCandidate.connect(
+ this, &MediaTransportHandlerSTS::OnCandidateFound);
+ }
+
+ // Begins an ICE restart if this stream has a different ufrag/pwd
+ stream->SetIceCredentials(aUfrag, aPwd);
+
+ // Make sure there's an entry in mTransports
+ mTransports[aTransportId];
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) {
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, keyDer = aKeyDer.Clone(), certDer = aCertDer.Clone(),
+ self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ MOZ_ASSERT(aComponentCount);
+ RefPtr<DtlsIdentity> dtlsIdentity(
+ DtlsIdentity::Deserialize(keyDer, certDer, aAuthType));
+ if (!dtlsIdentity) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
+ if (!stream) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ CSFLogDebug(LOGTAG, "%s: Activating ICE media stream=%s components=%u",
+ mIceCtx->name().c_str(), aTransportId.c_str(),
+ static_cast<unsigned>(aComponentCount));
+
+ std::vector<std::string> attrs;
+ attrs.reserve(2 /* ufrag + pwd */);
+ attrs.push_back("ice-ufrag:" + aUfrag);
+ attrs.push_back("ice-pwd:" + aPassword);
+
+ // If we started an ICE restart in EnsureProvisionalTransport, this is
+ // where we decide whether to commit or rollback.
+ nsresult rv = stream->ConnectToPeer(aLocalUfrag, aLocalPwd, attrs);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Couldn't parse ICE attributes, rv=%u",
+ static_cast<unsigned>(rv));
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ Transport transport = mTransports[aTransportId];
+ if (!transport.mFlow) {
+ transport.mFlow =
+ CreateTransportFlow(aTransportId, false, dtlsIdentity,
+ aDtlsClient, aDigests, aPrivacyRequested);
+ if (!transport.mFlow) {
+ return;
+ }
+ TransportLayer* dtls =
+ transport.mFlow->GetLayer(TransportLayerDtls::ID());
+ dtls->SignalStateChange.connect(
+ this, &MediaTransportHandlerSTS::OnStateChange);
+ if (aComponentCount < 2) {
+ dtls->SignalStateChange.connect(
+ this, &MediaTransportHandlerSTS::OnRtcpStateChange);
+ }
+ }
+
+ if (aComponentCount == 2) {
+ if (!transport.mRtcpFlow) {
+ transport.mRtcpFlow =
+ CreateTransportFlow(aTransportId, true, dtlsIdentity,
+ aDtlsClient, aDigests, aPrivacyRequested);
+ if (!transport.mRtcpFlow) {
+ return;
+ }
+ TransportLayer* dtls =
+ transport.mRtcpFlow->GetLayer(TransportLayerDtls::ID());
+ dtls->SignalStateChange.connect(
+ this, &MediaTransportHandlerSTS::OnRtcpStateChange);
+ }
+ } else {
+ transport.mRtcpFlow = nullptr;
+ // components are 1-indexed
+ stream->DisableComponent(2);
+ }
+
+ mTransports[aTransportId] = transport;
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::SetTargetForDefaultLocalAddressLookup(
+ const std::string& aTargetIp, uint16_t aTargetPort) {
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ mIceCtx->SetTargetForDefaultLocalAddressLookup(aTargetIp, aTargetPort);
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::StartIceGathering(
+ bool aDefaultRouteOnly, bool aObfuscateHostAddresses,
+ const nsTArray<NrIceStunAddr>& aStunAddrs) {
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, stunAddrs = aStunAddrs.Clone(),
+ self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ mObfuscateHostAddresses = aObfuscateHostAddresses;
+
+ // Belt and suspenders - in e10s mode, the call below to SetStunAddrs
+ // needs to have the proper flags set on ice ctx. For non-e10s,
+ // setting those flags happens in StartGathering. We could probably
+ // just set them here, and only do it here.
+ mIceCtx->SetCtxFlags(aDefaultRouteOnly);
+
+ if (stunAddrs.Length()) {
+ mIceCtx->SetStunAddrs(stunAddrs);
+ }
+
+ // Start gathering, but only if there are streams
+ if (!mIceCtx->GetStreams().empty()) {
+ mIceCtx->StartGathering(aDefaultRouteOnly, aObfuscateHostAddresses);
+ }
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::StartIceChecks(
+ bool aIsControlling, const std::vector<std::string>& aIceOptions) {
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ nsresult rv = mIceCtx->ParseGlobalAttributes(aIceOptions);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: couldn't parse global parameters",
+ __FUNCTION__);
+ return;
+ }
+
+ rv = mIceCtx->SetControlling(aIsControlling ? NrIceCtx::ICE_CONTROLLING
+ : NrIceCtx::ICE_CONTROLLED);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: couldn't set controlling to %d",
+ __FUNCTION__, aIsControlling);
+ return;
+ }
+
+ rv = mIceCtx->StartChecks();
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: couldn't start checks", __FUNCTION__);
+ return;
+ }
+ },
+ [](const std::string& aError) {});
+}
+
+void TokenizeCandidate(const std::string& aCandidate,
+ std::vector<std::string>& aTokens) {
+ aTokens.clear();
+
+ std::istringstream iss(aCandidate);
+ std::string token;
+ while (std::getline(iss, token, ' ')) {
+ aTokens.push_back(token);
+ }
+}
+
+void MediaTransportHandlerSTS::AddIceCandidate(
+ const std::string& aTransportId, const std::string& aCandidate,
+ const std::string& aUfrag, const std::string& aObfuscatedAddress) {
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ std::vector<std::string> tokens;
+ TokenizeCandidate(aCandidate, tokens);
+
+ RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
+ if (!stream) {
+ CSFLogError(LOGTAG,
+ "No ICE stream for candidate with transport id %s: %s",
+ aTransportId.c_str(), aCandidate.c_str());
+ return;
+ }
+
+ nsresult rv = stream->ParseTrickleCandidate(aCandidate, aUfrag,
+ aObfuscatedAddress);
+ if (NS_SUCCEEDED(rv)) {
+ // If the address is not obfuscated, we want to track it as
+ // explicitly signaled so that we know it is fine to reveal
+ // the address later on.
+ if (mObfuscateHostAddresses && tokens.size() > 4 &&
+ aObfuscatedAddress.empty()) {
+ mSignaledAddresses.insert(tokens[4]);
+ }
+ } else {
+ CSFLogError(LOGTAG,
+ "Couldn't process ICE candidate with transport id %s: "
+ "%s",
+ aTransportId.c_str(), aCandidate.c_str());
+ }
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::UpdateNetworkState(bool aOnline) {
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ mIceCtx->UpdateNetworkState(aOnline);
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) {
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ for (auto it = mTransports.begin(); it != mTransports.end();) {
+ const std::string transportId(it->first);
+ if (!aTransportIds.count(transportId)) {
+ if (it->second.mFlow) {
+ OnStateChange(transportId, TransportLayer::TS_NONE);
+ OnRtcpStateChange(transportId, TransportLayer::TS_NONE);
+ }
+ // Erase the transport before destroying the ice stream so that
+ // the close_notify alerts have a chance to be sent as the
+ // TransportFlow destructors execute.
+ it = mTransports.erase(it);
+ // We're already on the STS thread, but the TransportFlow
+ // destructor executed when mTransports.erase(it) is called
+ // above dispatches the call to DestroyFinal to the STS thread. If
+ // we don't also dispatch the call to destroy the NrIceMediaStream
+ // to the STS thread, it will tear down the NrIceMediaStream
+ // before the TransportFlow is destroyed. Without a valid
+ // NrIceMediaStream the close_notify alert cannot be sent.
+ mStsThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [iceCtx = RefPtr<NrIceCtx>(mIceCtx), transportId] {
+ iceCtx->DestroyStream(transportId);
+ }));
+ } else {
+ MOZ_ASSERT(it->second.mFlow);
+ ++it;
+ }
+ }
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) {
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [this, self = RefPtr<MediaTransportHandlerSTS>(this), aTransportId,
+ aPacket = std::move(aPacket)]() mutable {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ MOZ_ASSERT(aPacket.type() != MediaPacket::UNCLASSIFIED);
+ RefPtr<TransportFlow> flow =
+ GetTransportFlow(aTransportId, aPacket.type() == MediaPacket::RTCP);
+
+ if (!flow) {
+ CSFLogError(LOGTAG,
+ "%s: No such transport flow (%s) for outgoing packet",
+ mIceCtx->name().c_str(), aTransportId.c_str());
+ return;
+ }
+
+ TransportLayer* layer = nullptr;
+ switch (aPacket.type()) {
+ case MediaPacket::SCTP:
+ layer = flow->GetLayer(TransportLayerDtls::ID());
+ break;
+ case MediaPacket::RTP:
+ case MediaPacket::RTCP:
+ layer = flow->GetLayer(TransportLayerSrtp::ID());
+ break;
+ default:
+ // Maybe it would be useful to allow the injection of other packet
+ // types for testing?
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ MOZ_ASSERT(layer);
+
+ if (layer->SendPacket(aPacket) < 0) {
+ CSFLogError(LOGTAG, "%s: Transport flow (%s) failed to send packet",
+ mIceCtx->name().c_str(), aTransportId.c_str());
+ }
+ },
+ [](const std::string& aError) {});
+}
+
+TransportLayer::State MediaTransportHandler::GetState(
+ const std::string& aTransportId, bool aRtcp) const {
+ // TODO Bug 1520692: we should allow Datachannel to connect without
+ // DTLS SRTP keys
+ if (mCallbackThread) {
+ MOZ_ASSERT(mCallbackThread->IsOnCurrentThread());
+ }
+
+ const std::map<std::string, TransportLayer::State>* cache = nullptr;
+ if (aRtcp) {
+ cache = &mRtcpStateCache;
+ } else {
+ cache = &mStateCache;
+ }
+
+ auto it = cache->find(aTransportId);
+ if (it != cache->end()) {
+ return it->second;
+ }
+ return TransportLayer::TS_NONE;
+}
+
+void MediaTransportHandler::OnCandidate(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnCandidate, aTransportId,
+ aCandidateInfo),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SignalCandidate(aTransportId, aCandidateInfo);
+}
+
+void MediaTransportHandler::OnAlpnNegotiated(const std::string& aAlpn) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnAlpnNegotiated, aAlpn),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ const bool privacyRequested = aAlpn == "c-webrtc";
+ SignalAlpnNegotiated(aAlpn, privacyRequested);
+}
+
+void MediaTransportHandler::OnGatheringStateChange(
+ dom::RTCIceGatheringState aState) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnGatheringStateChange,
+ aState),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SignalGatheringStateChange(aState);
+}
+
+void MediaTransportHandler::OnConnectionStateChange(
+ dom::RTCIceConnectionState aState) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnConnectionStateChange,
+ aState),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SignalConnectionStateChange(aState);
+}
+
+void MediaTransportHandler::OnPacketReceived(const std::string& aTransportId,
+ const MediaPacket& aPacket) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnPacketReceived,
+ aTransportId, const_cast<MediaPacket&>(aPacket)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SignalPacketReceived(aTransportId, aPacket);
+}
+
+void MediaTransportHandler::OnEncryptedSending(const std::string& aTransportId,
+ const MediaPacket& aPacket) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnEncryptedSending,
+ aTransportId, const_cast<MediaPacket&>(aPacket)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SignalEncryptedSending(aTransportId, aPacket);
+}
+
+void MediaTransportHandler::OnStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnStateChange, aTransportId,
+ aState),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (aState == TransportLayer::TS_NONE) {
+ mStateCache.erase(aTransportId);
+ } else {
+ mStateCache[aTransportId] = aState;
+ }
+ SignalStateChange(aTransportId, aState);
+}
+
+void MediaTransportHandler::OnRtcpStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnRtcpStateChange,
+ aTransportId, aState),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (aState == TransportLayer::TS_NONE) {
+ mRtcpStateCache.erase(aTransportId);
+ } else {
+ mRtcpStateCache[aTransportId] = aState;
+ }
+ SignalRtcpStateChange(aTransportId, aState);
+}
+
+RefPtr<dom::RTCStatsPromise> MediaTransportHandlerSTS::GetIceStats(
+ const std::string& aTransportId, DOMHighResTimeStamp aNow) {
+ return mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ UniquePtr<dom::RTCStatsCollection> stats(new dom::RTCStatsCollection);
+ if (mIceCtx) {
+ for (const auto& stream : mIceCtx->GetStreams()) {
+ if (aTransportId.empty() || aTransportId == stream->GetId()) {
+ GetIceStats(*stream, aNow, stats.get());
+ }
+ }
+ }
+ return dom::RTCStatsPromise::CreateAndResolve(std::move(stats),
+ __func__);
+ });
+}
+
+RefPtr<MediaTransportHandler::IceLogPromise>
+MediaTransportHandlerSTS::GetIceLog(const nsCString& aPattern) {
+ return InvokeAsync(
+ mStsThread, __func__, [=, self = RefPtr<MediaTransportHandlerSTS>(this)] {
+ dom::Sequence<nsString> converted;
+ RLogConnector* logs = RLogConnector::GetInstance();
+ std::deque<std::string> result;
+ // Might not exist yet.
+ if (logs) {
+ logs->Filter(aPattern.get(), 0, &result);
+ }
+ /// XXX(Bug 1631386) Check if we should reject the promise instead of
+ /// crashing in an OOM situation.
+ if (!converted.SetCapacity(result.size(), fallible)) {
+ mozalloc_handle_oom(sizeof(nsString) * result.size());
+ }
+ for (auto& line : result) {
+ // Cannot fail, SetCapacity was called before.
+ (void)converted.AppendElement(NS_ConvertUTF8toUTF16(line.c_str()),
+ fallible);
+ }
+ return IceLogPromise::CreateAndResolve(std::move(converted), __func__);
+ });
+}
+
+void MediaTransportHandlerSTS::ClearIceLog() {
+ if (!mStsThread->IsOnCurrentThread()) {
+ mStsThread->Dispatch(WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this),
+ &MediaTransportHandlerSTS::ClearIceLog),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ RLogConnector* logs = RLogConnector::GetInstance();
+ if (logs) {
+ logs->Clear();
+ }
+}
+
+void MediaTransportHandlerSTS::EnterPrivateMode() {
+ if (!mStsThread->IsOnCurrentThread()) {
+ mStsThread->Dispatch(
+ WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this),
+ &MediaTransportHandlerSTS::EnterPrivateMode),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ RLogConnector::GetInstance()->EnterPrivateMode();
+}
+
+void MediaTransportHandlerSTS::ExitPrivateMode() {
+ if (!mStsThread->IsOnCurrentThread()) {
+ mStsThread->Dispatch(
+ WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this),
+ &MediaTransportHandlerSTS::ExitPrivateMode),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ auto* log = RLogConnector::GetInstance();
+ MOZ_ASSERT(log);
+ if (log) {
+ log->ExitPrivateMode();
+ }
+}
+
+static void ToRTCIceCandidateStats(
+ const std::vector<NrIceCandidate>& candidates,
+ dom::RTCStatsType candidateType, const nsString& transportId,
+ DOMHighResTimeStamp now, dom::RTCStatsCollection* stats,
+ bool obfuscateHostAddresses,
+ const std::set<std::string>& signaledAddresses) {
+ MOZ_ASSERT(stats);
+ for (const auto& candidate : candidates) {
+ dom::RTCIceCandidateStats cand;
+ cand.mType.Construct(candidateType);
+ NS_ConvertASCIItoUTF16 codeword(candidate.codeword.c_str());
+ cand.mTransportId.Construct(transportId);
+ cand.mId.Construct(codeword);
+ cand.mTimestamp.Construct(now);
+ cand.mCandidateType.Construct(dom::RTCIceCandidateType(candidate.type));
+ cand.mPriority.Construct(candidate.priority);
+ // https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03#section-3.3.1
+ // This obfuscates the address with the mDNS address if one exists
+ if (!candidate.mdns_addr.empty()) {
+ cand.mAddress.Construct(
+ NS_ConvertASCIItoUTF16(candidate.mdns_addr.c_str()));
+ } else if (obfuscateHostAddresses &&
+ candidate.type == NrIceCandidate::ICE_PEER_REFLEXIVE &&
+ signaledAddresses.find(candidate.cand_addr.host) ==
+ signaledAddresses.end()) {
+ cand.mAddress.Construct(NS_ConvertASCIItoUTF16("(redacted)"));
+ } else {
+ cand.mAddress.Construct(
+ NS_ConvertASCIItoUTF16(candidate.cand_addr.host.c_str()));
+ }
+ cand.mPort.Construct(candidate.cand_addr.port);
+ cand.mProtocol.Construct(
+ NS_ConvertASCIItoUTF16(candidate.cand_addr.transport.c_str()));
+ if (candidateType == dom::RTCStatsType::Local_candidate &&
+ dom::RTCIceCandidateType(candidate.type) ==
+ dom::RTCIceCandidateType::Relay) {
+ cand.mRelayProtocol.Construct(
+ NS_ConvertASCIItoUTF16(candidate.local_addr.transport.c_str()));
+ }
+ cand.mProxied.Construct(NS_ConvertASCIItoUTF16(
+ candidate.is_proxied ? "proxied" : "non-proxied"));
+ if (!stats->mIceCandidateStats.AppendElement(cand, fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ if (candidate.trickled) {
+ if (!stats->mTrickledIceCandidateStats.AppendElement(cand, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+}
+
+void MediaTransportHandlerSTS::GetIceStats(
+ const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow,
+ dom::RTCStatsCollection* aStats) const {
+ MOZ_ASSERT(mStsThread->IsOnCurrentThread());
+
+ NS_ConvertASCIItoUTF16 transportId(aStream.GetId().c_str());
+
+ std::vector<NrIceCandidatePair> candPairs;
+ nsresult res = aStream.GetCandidatePairs(&candPairs);
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG,
+ "%s: Error getting candidate pairs for transport id \"%s\"",
+ __FUNCTION__, aStream.GetId().c_str());
+ return;
+ }
+
+ for (auto& candPair : candPairs) {
+ NS_ConvertASCIItoUTF16 codeword(candPair.codeword.c_str());
+ NS_ConvertASCIItoUTF16 localCodeword(candPair.local.codeword.c_str());
+ NS_ConvertASCIItoUTF16 remoteCodeword(candPair.remote.codeword.c_str());
+ // Only expose candidate-pair statistics to chrome, until we've thought
+ // through the implications of exposing it to content.
+
+ dom::RTCIceCandidatePairStats s;
+ s.mId.Construct(codeword);
+ s.mTransportId.Construct(transportId);
+ s.mTimestamp.Construct(aNow);
+ s.mType.Construct(dom::RTCStatsType::Candidate_pair);
+ s.mLocalCandidateId.Construct(localCodeword);
+ s.mRemoteCandidateId.Construct(remoteCodeword);
+ s.mNominated.Construct(candPair.nominated);
+ s.mWritable.Construct(candPair.writable);
+ s.mReadable.Construct(candPair.readable);
+ s.mPriority.Construct(candPair.priority);
+ s.mSelected.Construct(candPair.selected);
+ s.mBytesSent.Construct(candPair.bytes_sent);
+ s.mBytesReceived.Construct(candPair.bytes_recvd);
+ s.mLastPacketSentTimestamp.Construct(candPair.ms_since_last_send);
+ s.mLastPacketReceivedTimestamp.Construct(candPair.ms_since_last_recv);
+ s.mState.Construct(dom::RTCStatsIceCandidatePairState(candPair.state));
+ s.mComponentId.Construct(candPair.component_id);
+ if (!aStats->mIceCandidatePairStats.AppendElement(s, fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ std::vector<NrIceCandidate> candidates;
+ if (NS_SUCCEEDED(aStream.GetLocalCandidates(&candidates))) {
+ ToRTCIceCandidateStats(candidates, dom::RTCStatsType::Local_candidate,
+ transportId, aNow, aStats, mObfuscateHostAddresses,
+ mSignaledAddresses);
+ // add the local candidates unparsed string to a sequence
+ for (const auto& candidate : candidates) {
+ if (!aStats->mRawLocalCandidates.AppendElement(
+ NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ candidates.clear();
+
+ if (NS_SUCCEEDED(aStream.GetRemoteCandidates(&candidates))) {
+ ToRTCIceCandidateStats(candidates, dom::RTCStatsType::Remote_candidate,
+ transportId, aNow, aStats, mObfuscateHostAddresses,
+ mSignaledAddresses);
+ // add the remote candidates unparsed string to a sequence
+ for (const auto& candidate : candidates) {
+ if (!aStats->mRawRemoteCandidates.AppendElement(
+ NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+}
+
+RefPtr<TransportFlow> MediaTransportHandlerSTS::GetTransportFlow(
+ const std::string& aTransportId, bool aIsRtcp) const {
+ auto it = mTransports.find(aTransportId);
+ if (it == mTransports.end()) {
+ return nullptr;
+ }
+
+ if (aIsRtcp) {
+ return it->second.mRtcpFlow ? it->second.mRtcpFlow : it->second.mFlow;
+ ;
+ }
+
+ return it->second.mFlow;
+}
+
+RefPtr<TransportFlow> MediaTransportHandlerSTS::CreateTransportFlow(
+ const std::string& aTransportId, bool aIsRtcp,
+ RefPtr<DtlsIdentity> aDtlsIdentity, bool aDtlsClient,
+ const DtlsDigestList& aDigests, bool aPrivacyRequested) {
+ nsresult rv;
+ RefPtr<TransportFlow> flow = new TransportFlow(aTransportId);
+
+ // The media streams are made on STS so we need to defer setup.
+ auto ice = MakeUnique<TransportLayerIce>();
+ auto dtls = MakeUnique<TransportLayerDtls>();
+ auto srtp = MakeUnique<TransportLayerSrtp>(*dtls);
+ dtls->SetRole(aDtlsClient ? TransportLayerDtls::CLIENT
+ : TransportLayerDtls::SERVER);
+
+ dtls->SetIdentity(aDtlsIdentity);
+
+ dtls->SetMinMaxVersion(
+ static_cast<TransportLayerDtls::Version>(mMinDtlsVersion),
+ static_cast<TransportLayerDtls::Version>(mMaxDtlsVersion));
+
+ for (const auto& digest : aDigests) {
+ rv = dtls->SetVerificationDigest(digest);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Could not set fingerprint");
+ return nullptr;
+ }
+ }
+
+ std::vector<uint16_t> srtpCiphers =
+ TransportLayerDtls::GetDefaultSrtpCiphers();
+
+ rv = dtls->SetSrtpCiphers(srtpCiphers);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Couldn't set SRTP ciphers");
+ return nullptr;
+ }
+
+ // Always permits negotiation of the confidential mode.
+ // Only allow non-confidential (which is an allowed default),
+ // if we aren't confidential.
+ std::set<std::string> alpn = {"c-webrtc"};
+ std::string alpnDefault;
+ if (!aPrivacyRequested) {
+ alpnDefault = "webrtc";
+ alpn.insert(alpnDefault);
+ }
+ rv = dtls->SetAlpn(alpn, alpnDefault);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Couldn't set ALPN");
+ return nullptr;
+ }
+
+ ice->SetParameters(mIceCtx->GetStream(aTransportId), aIsRtcp ? 2 : 1);
+ NS_ENSURE_SUCCESS(ice->Init(), nullptr);
+ NS_ENSURE_SUCCESS(dtls->Init(), nullptr);
+ NS_ENSURE_SUCCESS(srtp->Init(), nullptr);
+ dtls->Chain(ice.get());
+ srtp->Chain(ice.get());
+
+ dtls->SignalPacketReceived.connect(this,
+ &MediaTransportHandlerSTS::PacketReceived);
+ srtp->SignalPacketReceived.connect(this,
+ &MediaTransportHandlerSTS::PacketReceived);
+ ice->SignalPacketSending.connect(
+ this, &MediaTransportHandlerSTS::EncryptedPacketSending);
+ flow->PushLayer(ice.release());
+ flow->PushLayer(dtls.release());
+ flow->PushLayer(srtp.release());
+ return flow;
+}
+
+static mozilla::dom::RTCIceGatheringState toDomIceGatheringState(
+ NrIceCtx::GatheringState aState) {
+ switch (aState) {
+ case NrIceCtx::ICE_CTX_GATHER_INIT:
+ return dom::RTCIceGatheringState::New;
+ case NrIceCtx::ICE_CTX_GATHER_STARTED:
+ return dom::RTCIceGatheringState::Gathering;
+ case NrIceCtx::ICE_CTX_GATHER_COMPLETE:
+ return dom::RTCIceGatheringState::Complete;
+ }
+ MOZ_CRASH();
+}
+
+void MediaTransportHandlerSTS::OnGatheringStateChange(
+ NrIceCtx* aIceCtx, NrIceCtx::GatheringState aState) {
+ OnGatheringStateChange(toDomIceGatheringState(aState));
+}
+
+static mozilla::dom::RTCIceConnectionState toDomIceConnectionState(
+ NrIceCtx::ConnectionState aState) {
+ switch (aState) {
+ case NrIceCtx::ICE_CTX_INIT:
+ return dom::RTCIceConnectionState::New;
+ case NrIceCtx::ICE_CTX_CHECKING:
+ return dom::RTCIceConnectionState::Checking;
+ case NrIceCtx::ICE_CTX_CONNECTED:
+ return dom::RTCIceConnectionState::Connected;
+ case NrIceCtx::ICE_CTX_COMPLETED:
+ return dom::RTCIceConnectionState::Completed;
+ case NrIceCtx::ICE_CTX_FAILED:
+ return dom::RTCIceConnectionState::Failed;
+ case NrIceCtx::ICE_CTX_DISCONNECTED:
+ return dom::RTCIceConnectionState::Disconnected;
+ case NrIceCtx::ICE_CTX_CLOSED:
+ return dom::RTCIceConnectionState::Closed;
+ }
+ MOZ_CRASH();
+}
+
+void MediaTransportHandlerSTS::OnConnectionStateChange(
+ NrIceCtx* aIceCtx, NrIceCtx::ConnectionState aState) {
+ OnConnectionStateChange(toDomIceConnectionState(aState));
+}
+
+// The stuff below here will eventually go into the MediaTransportChild class
+void MediaTransportHandlerSTS::OnCandidateFound(
+ NrIceMediaStream* aStream, const std::string& aCandidate,
+ const std::string& aUfrag, const std::string& aMDNSAddr,
+ const std::string& aActualAddr) {
+ CandidateInfo info;
+ info.mCandidate = aCandidate;
+ MOZ_ASSERT(!aUfrag.empty());
+ info.mUfrag = aUfrag;
+ NrIceCandidate defaultRtpCandidate;
+ NrIceCandidate defaultRtcpCandidate;
+ nsresult rv = aStream->GetDefaultCandidate(1, &defaultRtpCandidate);
+ if (NS_SUCCEEDED(rv)) {
+ if (!defaultRtpCandidate.mdns_addr.empty()) {
+ info.mDefaultHostRtp = "0.0.0.0";
+ info.mDefaultPortRtp = 9;
+ } else {
+ info.mDefaultHostRtp = defaultRtpCandidate.cand_addr.host;
+ info.mDefaultPortRtp = defaultRtpCandidate.cand_addr.port;
+ }
+ } else {
+ CSFLogError(LOGTAG,
+ "%s: GetDefaultCandidates failed for transport id %s, "
+ "res=%u",
+ __FUNCTION__, aStream->GetId().c_str(),
+ static_cast<unsigned>(rv));
+ }
+
+ // Optional; component won't exist if doing rtcp-mux
+ if (NS_SUCCEEDED(aStream->GetDefaultCandidate(2, &defaultRtcpCandidate))) {
+ if (!defaultRtcpCandidate.mdns_addr.empty()) {
+ info.mDefaultHostRtcp = defaultRtcpCandidate.mdns_addr;
+ } else {
+ info.mDefaultHostRtcp = defaultRtcpCandidate.cand_addr.host;
+ }
+ info.mDefaultPortRtcp = defaultRtcpCandidate.cand_addr.port;
+ }
+
+ info.mMDNSAddress = aMDNSAddr;
+ info.mActualAddress = aActualAddr;
+
+ OnCandidate(aStream->GetId(), info);
+}
+
+void MediaTransportHandlerSTS::OnStateChange(TransportLayer* aLayer,
+ TransportLayer::State aState) {
+ if (aState == TransportLayer::TS_OPEN) {
+ MOZ_ASSERT(aLayer->id() == TransportLayerDtls::ID());
+ TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(aLayer);
+ OnAlpnNegotiated(dtlsLayer->GetNegotiatedAlpn());
+ }
+
+ // DTLS state indicates the readiness of the transport as a whole, because
+ // SRTP uses the keys from the DTLS handshake.
+ MediaTransportHandler::OnStateChange(aLayer->flow_id(), aState);
+}
+
+void MediaTransportHandlerSTS::OnRtcpStateChange(TransportLayer* aLayer,
+ TransportLayer::State aState) {
+ MediaTransportHandler::OnRtcpStateChange(aLayer->flow_id(), aState);
+}
+
+void MediaTransportHandlerSTS::PacketReceived(TransportLayer* aLayer,
+ MediaPacket& aPacket) {
+ OnPacketReceived(aLayer->flow_id(), aPacket);
+}
+
+void MediaTransportHandlerSTS::EncryptedPacketSending(TransportLayer* aLayer,
+ MediaPacket& aPacket) {
+ OnEncryptedSending(aLayer->flow_id(), aPacket);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.h b/dom/media/webrtc/jsapi/MediaTransportHandler.h
new file mode 100644
index 0000000000..f8f355d665
--- /dev/null
+++ b/dom/media/webrtc/jsapi/MediaTransportHandler.h
@@ -0,0 +1,163 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _MTRANSPORTHANDLER_H__
+#define _MTRANSPORTHANDLER_H__
+
+#include "mozilla/RefPtr.h"
+#include "nsISupportsImpl.h"
+#include "transport/sigslot.h"
+#include "transport/transportlayer.h" // Need the State enum
+#include "transport/dtlsidentity.h" // For DtlsDigest
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+#include "mozilla/dom/RTCConfigurationBinding.h"
+#include "transport/nricectx.h" // Need some enums
+#include "common/CandidateInfo.h"
+#include "transport/nr_socket_proxy_config.h"
+#include "RTCStatsReport.h"
+
+#include "nsString.h"
+
+#include <string>
+#include <set>
+#include <vector>
+
+namespace mozilla {
+class DtlsIdentity;
+class NrIceCtx;
+class NrIceMediaStream;
+class NrIceResolver;
+class TransportFlow;
+class RTCStatsQuery;
+
+namespace dom {
+struct RTCStatsReportInternal;
+}
+
+class MediaTransportHandler {
+ public:
+ // Creates either a MediaTransportHandlerSTS or a MediaTransportHandlerIPC,
+ // as appropriate. If you want signals to fire on a specific thread, pass
+ // the event target here, otherwise they will fire on whatever is convenient.
+ // Note: This also determines what thread the state cache is updated on!
+ // Don't call GetState on any other thread!
+ static already_AddRefed<MediaTransportHandler> Create(
+ nsISerialEventTarget* aCallbackThread);
+
+ explicit MediaTransportHandler(nsISerialEventTarget* aCallbackThread)
+ : mCallbackThread(aCallbackThread) {}
+
+ static nsresult ConvertIceServers(
+ const nsTArray<dom::RTCIceServer>& aIceServers,
+ std::vector<NrIceStunServer>* aStunServers,
+ std::vector<NrIceTurnServer>* aTurnServers);
+
+ typedef MozPromise<dom::Sequence<nsString>, nsresult, true> IceLogPromise;
+
+ // There's a wrinkle here; the ICE logging is not separated out by
+ // MediaTransportHandler. These are a little more like static methods, but
+ // to avoid needing yet another IPC interface, we bolt them on here.
+ virtual RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) = 0;
+ virtual void ClearIceLog() = 0;
+ virtual void EnterPrivateMode() = 0;
+ virtual void ExitPrivateMode() = 0;
+
+ virtual nsresult CreateIceCtx(const std::string& aName,
+ const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) = 0;
+
+ // We will probably be able to move the proxy lookup stuff into
+ // this class once we move mtransport to its own process.
+ virtual void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) = 0;
+
+ virtual void EnsureProvisionalTransport(const std::string& aTransportId,
+ const std::string& aLocalUfrag,
+ const std::string& aLocalPwd,
+ size_t aComponentCount) = 0;
+
+ virtual void SetTargetForDefaultLocalAddressLookup(
+ const std::string& aTargetIp, uint16_t aTargetPort) = 0;
+
+ // We set default-route-only as late as possible because it depends on what
+ // capture permissions have been granted on the window, which could easily
+ // change between Init (ie; when the PC is created) and StartIceGathering
+ // (ie; when we set the local description).
+ virtual void StartIceGathering(bool aDefaultRouteOnly,
+ bool aObfuscateHostAddresses,
+ // TODO: It probably makes sense to look
+ // this up internally
+ const nsTArray<NrIceStunAddr>& aStunAddrs) = 0;
+
+ virtual void ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) = 0;
+
+ virtual void RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) = 0;
+
+ virtual void StartIceChecks(bool aIsControlling,
+ const std::vector<std::string>& aIceOptions) = 0;
+
+ virtual void SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) = 0;
+
+ virtual void AddIceCandidate(const std::string& aTransportId,
+ const std::string& aCandidate,
+ const std::string& aUFrag,
+ const std::string& aObfuscatedAddress) = 0;
+
+ virtual void UpdateNetworkState(bool aOnline) = 0;
+
+ virtual RefPtr<dom::RTCStatsPromise> GetIceStats(
+ const std::string& aTransportId, DOMHighResTimeStamp aNow) = 0;
+
+ sigslot::signal2<const std::string&, const CandidateInfo&> SignalCandidate;
+ sigslot::signal2<const std::string&, bool> SignalAlpnNegotiated;
+ sigslot::signal1<dom::RTCIceGatheringState> SignalGatheringStateChange;
+ sigslot::signal1<dom::RTCIceConnectionState> SignalConnectionStateChange;
+
+ sigslot::signal2<const std::string&, const MediaPacket&> SignalPacketReceived;
+ sigslot::signal2<const std::string&, const MediaPacket&>
+ SignalEncryptedSending;
+ sigslot::signal2<const std::string&, TransportLayer::State> SignalStateChange;
+ sigslot::signal2<const std::string&, TransportLayer::State>
+ SignalRtcpStateChange;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(MediaTransportHandler,
+ Destroy())
+
+ TransportLayer::State GetState(const std::string& aTransportId,
+ bool aRtcp) const;
+
+ protected:
+ void OnCandidate(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo);
+ void OnAlpnNegotiated(const std::string& aAlpn);
+ void OnGatheringStateChange(dom::RTCIceGatheringState aState);
+ void OnConnectionStateChange(dom::RTCIceConnectionState aState);
+ void OnPacketReceived(const std::string& aTransportId,
+ const MediaPacket& aPacket);
+ void OnEncryptedSending(const std::string& aTransportId,
+ const MediaPacket& aPacket);
+ void OnStateChange(const std::string& aTransportId,
+ TransportLayer::State aState);
+ void OnRtcpStateChange(const std::string& aTransportId,
+ TransportLayer::State aState);
+ virtual void Destroy() = 0;
+ virtual ~MediaTransportHandler() = default;
+ std::map<std::string, TransportLayer::State> mStateCache;
+ std::map<std::string, TransportLayer::State> mRtcpStateCache;
+ RefPtr<nsISerialEventTarget> mCallbackThread;
+};
+
+void TokenizeCandidate(const std::string& aCandidate,
+ std::vector<std::string>& aTokens);
+
+} // namespace mozilla
+
+#endif //_MTRANSPORTHANDLER_H__
diff --git a/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp
new file mode 100644
index 0000000000..70316b51ae
--- /dev/null
+++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp
@@ -0,0 +1,398 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "MediaTransportHandlerIPC.h"
+#include "mozilla/dom/MediaTransportChild.h"
+#include "nsThreadUtils.h"
+#include "mozilla/net/SocketProcessBridgeChild.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "common/browser_logging/CSFLog.h"
+
+namespace mozilla {
+
+static const char* mthipcLogTag = "MediaTransportHandler";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG mthipcLogTag
+
+MediaTransportHandlerIPC::MediaTransportHandlerIPC(
+ nsISerialEventTarget* aCallbackThread)
+ : MediaTransportHandler(aCallbackThread) {
+ mInitPromise = net::SocketProcessBridgeChild::GetSocketProcessBridge()->Then(
+ mCallbackThread, __func__,
+ [this, self = RefPtr<MediaTransportHandlerIPC>(this)](
+ const RefPtr<net::SocketProcessBridgeChild>& aBridge) {
+ ipc::PBackgroundChild* actor =
+ ipc::BackgroundChild::GetOrCreateSocketActorForCurrentThread();
+ if (!actor) {
+ NS_WARNING(
+ "MediaTransportHandlerIPC async init failed! Webrtc networking "
+ "will not work!");
+ return InitPromise::CreateAndReject(
+ nsCString("GetOrCreateSocketActorForCurrentThread failed!"),
+ __func__);
+ }
+ MediaTransportChild* child = new MediaTransportChild(this);
+ actor->SetEventTargetForActor(child, mCallbackThread);
+ // PBackgroungChild owns mChild! When it is done with it,
+ // mChild will let us know it it going away.
+ mChild = actor->SendPMediaTransportConstructor(child);
+ CSFLogDebug(LOGTAG, "%s Init done", __func__);
+ return InitPromise::CreateAndResolve(true, __func__);
+ },
+ [=](const nsCString& aError) {
+ CSFLogError(LOGTAG,
+ "MediaTransportHandlerIPC async init failed! Webrtc "
+ "networking will not work! Error was %s",
+ aError.get());
+ NS_WARNING(
+ "MediaTransportHandlerIPC async init failed! Webrtc networking "
+ "will not work!");
+ return InitPromise::CreateAndReject(aError, __func__);
+ });
+}
+
+RefPtr<MediaTransportHandler::IceLogPromise>
+MediaTransportHandlerIPC::GetIceLog(const nsCString& aPattern) {
+ return mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /* dummy */) {
+ if (!mChild) {
+ return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+ // Compiler has trouble deducing the return type here for some reason,
+ // so we use a temp variable as a hint.
+ // SendGetIceLog _almost_ returns an IceLogPromise; the reject value
+ // differs (ipc::ResponseRejectReason vs nsresult) so we need to
+ // convert.
+ RefPtr<IceLogPromise> promise = mChild->SendGetIceLog(aPattern)->Then(
+ mCallbackThread, __func__,
+ [](WebrtcGlobalLog&& aLogLines) {
+ return IceLogPromise::CreateAndResolve(std::move(aLogLines),
+ __func__);
+ },
+ [](ipc::ResponseRejectReason aReason) {
+ return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ });
+ return promise;
+ },
+ [](const nsCString& aError) {
+ return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ });
+}
+
+void MediaTransportHandlerIPC::ClearIceLog() {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendClearIceLog();
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::EnterPrivateMode() {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendEnterPrivateMode();
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::ExitPrivateMode() {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendExitPrivateMode();
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+nsresult MediaTransportHandlerIPC::CreateIceCtx(
+ const std::string& aName, const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) {
+ CSFLogDebug(LOGTAG, "MediaTransportHandlerIPC::CreateIceCtx start");
+ // Run some validation on this side of the IPC boundary so we can return
+ // errors synchronously. We don't actually use the results. It might make
+ // sense to move this check to PeerConnection and have this API take the
+ // converted form, but we would need to write IPC serialization code for
+ // the NrIce*Server types.
+ std::vector<NrIceStunServer> stunServers;
+ std::vector<NrIceTurnServer> turnServers;
+ nsresult rv = ConvertIceServers(aIceServers, &stunServers, &turnServers);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, iceServers = aIceServers.Clone(),
+ self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ CSFLogDebug(LOGTAG, "%s starting", __func__);
+ if (!mChild->SendCreateIceCtx(aName, std::move(iceServers),
+ aIcePolicy)) {
+ CSFLogError(LOGTAG, "%s failed!", __func__);
+ }
+ }
+ },
+ [](const nsCString& aError) {});
+
+ return NS_OK;
+}
+
+void MediaTransportHandlerIPC::Destroy() {
+ if (mChild) {
+ MediaTransportChild::Send__delete__(mChild);
+ mChild = nullptr;
+ }
+ delete this;
+}
+
+// We will probably be able to move the proxy lookup stuff into
+// this class once we move mtransport to its own process.
+void MediaTransportHandlerIPC::SetProxyConfig(
+ NrSocketProxyConfig&& aProxyConfig) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [aProxyConfig = std::move(aProxyConfig), this,
+ self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) mutable {
+ if (mChild) {
+ mChild->SendSetProxyConfig(aProxyConfig.GetConfig());
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::EnsureProvisionalTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendEnsureProvisionalTransport(aTransportId, aLocalUfrag,
+ aLocalPwd, aComponentCount);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::SetTargetForDefaultLocalAddressLookup(
+ const std::string& aTargetIp, uint16_t aTargetPort) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendSetTargetForDefaultLocalAddressLookup(aTargetIp,
+ aTargetPort);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+// We set default-route-only as late as possible because it depends on what
+// capture permissions have been granted on the window, which could easily
+// change between Init (ie; when the PC is created) and StartIceGathering
+// (ie; when we set the local description).
+void MediaTransportHandlerIPC::StartIceGathering(
+ bool aDefaultRouteOnly, bool aObfuscateHostAddresses,
+ // TODO(bug 1522205): It probably makes sense to look this up internally
+ const nsTArray<NrIceStunAddr>& aStunAddrs) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, stunAddrs = aStunAddrs.Clone(),
+ self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendStartIceGathering(aDefaultRouteOnly,
+ aObfuscateHostAddresses, stunAddrs);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, keyDer = aKeyDer.Clone(), certDer = aCertDer.Clone(),
+ self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendActivateTransport(aTransportId, aLocalUfrag, aLocalPwd,
+ aComponentCount, aUfrag, aPassword,
+ keyDer, certDer, aAuthType, aDtlsClient,
+ aDigests, aPrivacyRequested);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) {
+ std::vector<std::string> transportIds(aTransportIds.begin(),
+ aTransportIds.end());
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendRemoveTransportsExcept(transportIds);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::StartIceChecks(
+ bool aIsControlling, const std::vector<std::string>& aIceOptions) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendStartIceChecks(aIsControlling, aIceOptions);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [this, self = RefPtr<MediaTransportHandlerIPC>(this), aTransportId,
+ aPacket = std::move(aPacket)](bool /*dummy*/) mutable {
+ if (mChild) {
+ mChild->SendSendPacket(aTransportId, aPacket);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::AddIceCandidate(
+ const std::string& aTransportId, const std::string& aCandidate,
+ const std::string& aUfrag, const std::string& aObfuscatedAddress) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendAddIceCandidate(aTransportId, aCandidate, aUfrag,
+ aObfuscatedAddress);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::UpdateNetworkState(bool aOnline) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendUpdateNetworkState(aOnline);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+RefPtr<dom::RTCStatsPromise> MediaTransportHandlerIPC::GetIceStats(
+ const std::string& aTransportId, DOMHighResTimeStamp aNow) {
+ return mInitPromise->Then(
+ mCallbackThread, __func__,
+ [aTransportId, aNow, this,
+ self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (!mChild) {
+ return dom::RTCStatsPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+ RefPtr<dom::RTCStatsPromise> promise =
+ mChild->SendGetIceStats(aTransportId, aNow)
+ ->Then(
+ mCallbackThread, __func__,
+ [](const dom::RTCStatsCollection& aStats) {
+ UniquePtr<dom::RTCStatsCollection> stats(
+ new dom::RTCStatsCollection(aStats));
+ return dom::RTCStatsPromise::CreateAndResolve(
+ std::move(stats), __func__);
+ },
+ [](ipc::ResponseRejectReason aReason) {
+ return dom::RTCStatsPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ });
+ return promise;
+ },
+ [](const nsCString& aError) {
+ return dom::RTCStatsPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ });
+}
+
+MediaTransportChild::MediaTransportChild(MediaTransportHandlerIPC* aUser)
+ : mUser(aUser) {}
+
+MediaTransportChild::~MediaTransportChild() { mUser->mChild = nullptr; }
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnCandidate(
+ const string& transportId, const CandidateInfo& candidateInfo) {
+ mUser->OnCandidate(transportId, candidateInfo);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnAlpnNegotiated(
+ const string& alpn) {
+ mUser->OnAlpnNegotiated(alpn);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnGatheringStateChange(
+ const int& state) {
+ mUser->OnGatheringStateChange(static_cast<dom::RTCIceGatheringState>(state));
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnConnectionStateChange(
+ const int& state) {
+ mUser->OnConnectionStateChange(
+ static_cast<dom::RTCIceConnectionState>(state));
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnPacketReceived(
+ const string& transportId, const MediaPacket& packet) {
+ MediaPacket copy(packet); // Laaaaaame! Might be safe to const_cast?
+ mUser->OnPacketReceived(transportId, copy);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnEncryptedSending(
+ const string& transportId, const MediaPacket& packet) {
+ MediaPacket copy(packet); // Laaaaaame! Might be safe to const_cast?
+ mUser->OnEncryptedSending(transportId, copy);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnStateChange(
+ const string& transportId, const int& state) {
+ mUser->OnStateChange(transportId, static_cast<TransportLayer::State>(state));
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnRtcpStateChange(
+ const string& transportId, const int& state) {
+ mUser->OnRtcpStateChange(transportId,
+ static_cast<TransportLayer::State>(state));
+ return ipc::IPCResult::Ok();
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h
new file mode 100644
index 0000000000..05df596ca3
--- /dev/null
+++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _MTRANSPORTHANDLER_IPC_H__
+#define _MTRANSPORTHANDLER_IPC_H__
+
+#include "jsapi/MediaTransportHandler.h"
+#include "mozilla/dom/PMediaTransportChild.h"
+
+namespace mozilla {
+
+class MediaTransportChild;
+
+// Implementation of MediaTransportHandler that uses IPC (PMediaTransport) to
+// talk to mtransport on another process.
+class MediaTransportHandlerIPC : public MediaTransportHandler {
+ public:
+ explicit MediaTransportHandlerIPC(nsISerialEventTarget* aCallbackThread);
+ RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) override;
+ void ClearIceLog() override;
+ void EnterPrivateMode() override;
+ void ExitPrivateMode() override;
+
+ nsresult CreateIceCtx(const std::string& aName,
+ const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) override;
+
+ // We will probably be able to move the proxy lookup stuff into
+ // this class once we move mtransport to its own process.
+ void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override;
+
+ void EnsureProvisionalTransport(const std::string& aTransportId,
+ const std::string& aLocalUfrag,
+ const std::string& aLocalPwd,
+ size_t aComponentCount) override;
+
+ void SetTargetForDefaultLocalAddressLookup(const std::string& aTargetIp,
+ uint16_t aTargetPort) override;
+
+ // We set default-route-only as late as possible because it depends on what
+ // capture permissions have been granted on the window, which could easily
+ // change between Init (ie; when the PC is created) and StartIceGathering
+ // (ie; when we set the local description).
+ void StartIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses,
+ // TODO: It probably makes sense to look
+ // this up internally
+ const nsTArray<NrIceStunAddr>& aStunAddrs) override;
+
+ void ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) override;
+
+ void RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) override;
+
+ void StartIceChecks(bool aIsControlling,
+ const std::vector<std::string>& aIceOptions) override;
+
+ void SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) override;
+
+ void AddIceCandidate(const std::string& aTransportId,
+ const std::string& aCandidate, const std::string& aUfrag,
+ const std::string& aObfuscatedAddress) override;
+
+ void UpdateNetworkState(bool aOnline) override;
+
+ RefPtr<dom::RTCStatsPromise> GetIceStats(const std::string& aTransportId,
+ DOMHighResTimeStamp aNow) override;
+
+ private:
+ friend class MediaTransportChild;
+ void Destroy() override;
+
+ // We do not own this; it will tell us when it is going away.
+ dom::PMediaTransportChild* mChild = nullptr;
+
+ // |mChild| can only be initted asynchronously, |mInitPromise| resolves
+ // when that happens. The |Then| calls make it convenient to dispatch API
+ // calls to main, which is a bonus.
+ // Init promise is not exclusive; this lets us call |Then| on it for every
+ // API call we get, instead of creating another promise each time.
+ typedef MozPromise<bool, nsCString, false> InitPromise;
+ RefPtr<InitPromise> mInitPromise;
+};
+
+} // namespace mozilla
+
+#endif //_MTRANSPORTHANDLER_IPC_H__
diff --git a/dom/media/webrtc/jsapi/MediaTransportParent.cpp b/dom/media/webrtc/jsapi/MediaTransportParent.cpp
new file mode 100644
index 0000000000..5ac9ca3594
--- /dev/null
+++ b/dom/media/webrtc/jsapi/MediaTransportParent.cpp
@@ -0,0 +1,238 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "mozilla/dom/MediaTransportParent.h"
+#include "jsapi/MediaTransportHandler.h"
+
+#include "transport/sigslot.h"
+#include "common/browser_logging/CSFLog.h"
+
+namespace mozilla {
+
+// Deals with the MediaTransportHandler interface, so MediaTransportParent
+// doesn't have to..
+class MediaTransportParent::Impl : public sigslot::has_slots<> {
+ public:
+ explicit Impl(MediaTransportParent* aParent)
+ : mHandler(MediaTransportHandler::Create(GetCurrentSerialEventTarget())),
+ mParent(aParent) {
+ mHandler->SignalCandidate.connect(this,
+ &MediaTransportParent::Impl::OnCandidate);
+ mHandler->SignalAlpnNegotiated.connect(
+ this, &MediaTransportParent::Impl::OnAlpnNegotiated);
+ mHandler->SignalGatheringStateChange.connect(
+ this, &MediaTransportParent::Impl::OnGatheringStateChange);
+ mHandler->SignalConnectionStateChange.connect(
+ this, &MediaTransportParent::Impl::OnConnectionStateChange);
+ mHandler->SignalPacketReceived.connect(
+ this, &MediaTransportParent::Impl::OnPacketReceived);
+ mHandler->SignalEncryptedSending.connect(
+ this, &MediaTransportParent::Impl::OnEncryptedSending);
+ mHandler->SignalStateChange.connect(
+ this, &MediaTransportParent::Impl::OnStateChange);
+ mHandler->SignalRtcpStateChange.connect(
+ this, &MediaTransportParent::Impl::OnRtcpStateChange);
+ }
+
+ virtual ~Impl() {
+ disconnect_all();
+ mHandler = nullptr;
+ }
+
+ void OnCandidate(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnCandidate(aTransportId, aCandidateInfo));
+ }
+
+ void OnAlpnNegotiated(const std::string& aAlpn, bool aPrivacyRequested) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnAlpnNegotiated(aAlpn));
+ }
+
+ void OnGatheringStateChange(dom::RTCIceGatheringState aState) {
+ NS_ENSURE_TRUE_VOID(
+ mParent->SendOnGatheringStateChange(static_cast<int>(aState)));
+ }
+
+ void OnConnectionStateChange(dom::RTCIceConnectionState aState) {
+ NS_ENSURE_TRUE_VOID(
+ mParent->SendOnConnectionStateChange(static_cast<int>(aState)));
+ }
+
+ void OnPacketReceived(const std::string& aTransportId,
+ const MediaPacket& aPacket) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnPacketReceived(aTransportId, aPacket));
+ }
+
+ void OnEncryptedSending(const std::string& aTransportId,
+ const MediaPacket& aPacket) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnEncryptedSending(aTransportId, aPacket));
+ }
+
+ void OnStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnStateChange(aTransportId, aState));
+ }
+
+ void OnRtcpStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnRtcpStateChange(aTransportId, aState));
+ }
+
+ RefPtr<MediaTransportHandler> mHandler;
+
+ private:
+ MediaTransportParent* mParent;
+};
+
+MediaTransportParent::MediaTransportParent() : mImpl(new Impl(this)) {}
+
+MediaTransportParent::~MediaTransportParent() {}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvGetIceLog(
+ const nsCString& pattern, GetIceLogResolver&& aResolve) {
+ mImpl->mHandler->GetIceLog(pattern)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // IPDL doesn't give us a reject function, so we cannot reject async, so
+ // we are forced to resolve with an empty result. Laaaaaaame.
+ [aResolve = std::move(aResolve)](
+ MediaTransportHandler::IceLogPromise::ResolveOrRejectValue&&
+ aResult) mutable {
+ WebrtcGlobalLog logLines;
+ if (aResult.IsResolve()) {
+ logLines = std::move(aResult.ResolveValue());
+ }
+ aResolve(logLines);
+ });
+
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvClearIceLog() {
+ mImpl->mHandler->ClearIceLog();
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvEnterPrivateMode() {
+ mImpl->mHandler->EnterPrivateMode();
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvExitPrivateMode() {
+ mImpl->mHandler->ExitPrivateMode();
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvCreateIceCtx(
+ const string& name, nsTArray<RTCIceServer>&& iceServers,
+ const RTCIceTransportPolicy& icePolicy) {
+ nsresult rv = mImpl->mHandler->CreateIceCtx(name, iceServers, icePolicy);
+ if (NS_FAILED(rv)) {
+ return ipc::IPCResult::Fail(WrapNotNull(this), __func__,
+ "MediaTransportHandler::Init failed");
+ }
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvSetProxyConfig(
+ const net::WebrtcProxyConfig& aProxyConfig) {
+ mImpl->mHandler->SetProxyConfig(NrSocketProxyConfig(aProxyConfig));
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvEnsureProvisionalTransport(
+ const string& transportId, const string& localUfrag, const string& localPwd,
+ const int& componentCount) {
+ mImpl->mHandler->EnsureProvisionalTransport(transportId, localUfrag, localPwd,
+ componentCount);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult
+MediaTransportParent::RecvSetTargetForDefaultLocalAddressLookup(
+ const std::string& targetIp, uint16_t targetPort) {
+ mImpl->mHandler->SetTargetForDefaultLocalAddressLookup(targetIp, targetPort);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvStartIceGathering(
+ const bool& defaultRouteOnly, const bool& obfuscateHostAddresses,
+ const net::NrIceStunAddrArray& stunAddrs) {
+ mImpl->mHandler->StartIceGathering(defaultRouteOnly, obfuscateHostAddresses,
+ stunAddrs);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvActivateTransport(
+ const string& transportId, const string& localUfrag, const string& localPwd,
+ const int& componentCount, const string& remoteUfrag,
+ const string& remotePwd, nsTArray<uint8_t>&& keyDer,
+ nsTArray<uint8_t>&& certDer, const int& authType, const bool& dtlsClient,
+ const DtlsDigestList& digests, const bool& privacyRequested) {
+ mImpl->mHandler->ActivateTransport(
+ transportId, localUfrag, localPwd, componentCount, remoteUfrag, remotePwd,
+ keyDer, certDer, static_cast<SSLKEAType>(authType), dtlsClient, digests,
+ privacyRequested);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvRemoveTransportsExcept(
+ const StringVector& transportIds) {
+ std::set<std::string> ids(transportIds.begin(), transportIds.end());
+ mImpl->mHandler->RemoveTransportsExcept(ids);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvStartIceChecks(
+ const bool& isControlling, const StringVector& iceOptions) {
+ mImpl->mHandler->StartIceChecks(isControlling, iceOptions);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvSendPacket(
+ const string& transportId, const MediaPacket& packet) {
+ MediaPacket copy(packet); // Laaaaaaame.
+ mImpl->mHandler->SendPacket(transportId, std::move(copy));
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvAddIceCandidate(
+ const string& transportId, const string& candidate, const string& ufrag,
+ const string& obfuscatedAddr) {
+ mImpl->mHandler->AddIceCandidate(transportId, candidate, ufrag,
+ obfuscatedAddr);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvUpdateNetworkState(
+ const bool& online) {
+ mImpl->mHandler->UpdateNetworkState(online);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvGetIceStats(
+ const string& transportId, const double& now,
+ GetIceStatsResolver&& aResolve) {
+ mImpl->mHandler->GetIceStats(transportId, now)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // IPDL doesn't give us a reject function, so we cannot reject async,
+ // so we are forced to resolve with an unmodified result. Laaaaaaame.
+ [aResolve = std::move(aResolve)](
+ dom::RTCStatsPromise::ResolveOrRejectValue&& aResult) {
+ if (aResult.IsResolve()) {
+ aResolve(
+ dom::NotReallyMovableButLetsPretendItIsRTCStatsCollection(
+ *aResult.ResolveValue()));
+ } else {
+ dom::NotReallyMovableButLetsPretendItIsRTCStatsCollection empty;
+ aResolve(empty);
+ }
+ });
+
+ return ipc::IPCResult::Ok();
+}
+
+void MediaTransportParent::ActorDestroy(ActorDestroyReason aWhy) {}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/PacketDumper.cpp b/dom/media/webrtc/jsapi/PacketDumper.cpp
new file mode 100644
index 0000000000..92bb492c10
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PacketDumper.cpp
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "jsapi/PacketDumper.h"
+#include "jsapi/PeerConnectionImpl.h"
+#include "mozilla/media/MediaUtils.h" // NewRunnableFrom
+#include "nsThreadUtils.h" // NS_DispatchToMainThread
+
+namespace mozilla {
+
+PacketDumper::PacketDumper(PeerConnectionImpl* aPc) : mPc(aPc) {}
+
+PacketDumper::PacketDumper(const std::string& aPcHandle) {
+ if (!aPcHandle.empty()) {
+ PeerConnectionWrapper pcw(aPcHandle);
+ mPc = pcw.impl();
+ }
+}
+
+PacketDumper::~PacketDumper() {
+ RefPtr<Runnable> pcDisposeRunnable = media::NewRunnableFrom(std::bind(
+ [](RefPtr<PeerConnectionImpl> pc) { return NS_OK; }, mPc.forget()));
+ NS_DispatchToMainThread(pcDisposeRunnable);
+}
+
+void PacketDumper::Dump(size_t level, dom::mozPacketDumpType type, bool sending,
+ const void* data, size_t size) {
+ // Optimization; avoids making a copy of the buffer, but we need to lock a
+ // mutex and check the flags. Could be optimized further, if we really want to
+ if (!mPc || !mPc->ShouldDumpPacket(level, type, sending)) {
+ return;
+ }
+
+ RefPtr<PeerConnectionImpl> pc = mPc;
+
+ UniquePtr<uint8_t[]> ownedPacket = MakeUnique<uint8_t[]>(size);
+ memcpy(ownedPacket.get(), data, size);
+
+ RefPtr<Runnable> dumpRunnable = media::NewRunnableFrom(std::bind(
+ [pc, level, type, sending,
+ size](UniquePtr<uint8_t[]>& packet) -> nsresult {
+ pc->DumpPacket_m(level, type, sending, packet, size);
+ return NS_OK;
+ },
+ std::move(ownedPacket)));
+
+ NS_DispatchToMainThread(dumpRunnable);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/PacketDumper.h b/dom/media/webrtc/jsapi/PacketDumper.h
new file mode 100644
index 0000000000..fdf1c8900e
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PacketDumper.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _PACKET_DUMPER_H_
+#define _PACKET_DUMPER_H_
+
+#include "nsISupportsImpl.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+
+namespace mozilla {
+class PeerConnectionImpl;
+
+class PacketDumper {
+ public:
+ explicit PacketDumper(PeerConnectionImpl* aPc);
+ explicit PacketDumper(const std::string& aPcHandle);
+ PacketDumper(const PacketDumper&) = delete;
+ ~PacketDumper();
+
+ PacketDumper& operator=(const PacketDumper&) = delete;
+
+ void Dump(size_t level, dom::mozPacketDumpType type, bool sending,
+ const void* data, size_t size);
+
+ private:
+ RefPtr<PeerConnectionImpl> mPc;
+};
+
+} // namespace mozilla
+
+#endif // _PACKET_DUMPER_H_
diff --git a/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp b/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp
new file mode 100644
index 0000000000..9ae9e83adc
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp
@@ -0,0 +1,409 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "common/browser_logging/CSFLog.h"
+
+#include "PeerConnectionImpl.h"
+#include "PeerConnectionCtx.h"
+#include "transport/runnable_utils.h"
+#include "prcvar.h"
+
+#include "mozilla/Telemetry.h"
+#include "common/browser_logging/WebRtcLog.h"
+
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+#include "mozilla/Preferences.h"
+#include <mozilla/Types.h>
+
+#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID
+#include "nsServiceManagerUtils.h" // do_GetService
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+
+#include "nsCRTGlue.h"
+#include "nsIIOService.h"
+
+#include "gmp-video-decode.h" // GMP_API_VIDEO_DECODER
+#include "gmp-video-encode.h" // GMP_API_VIDEO_ENCODER
+
+static const char* pccLogTag = "PeerConnectionCtx";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG pccLogTag
+
+namespace mozilla {
+
+using namespace dom;
+
+class PeerConnectionCtxObserver : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ PeerConnectionCtxObserver() {}
+
+ void Init() {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (!observerService) return;
+
+ nsresult rv = NS_OK;
+
+ rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ false);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ rv = observerService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ false);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ (void)rv;
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ CSFLogDebug(LOGTAG, "Shutting down PeerConnectionCtx");
+ PeerConnectionCtx::Destroy();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (!observerService) return NS_ERROR_FAILURE;
+
+ nsresult rv = observerService->RemoveObserver(
+ this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ rv = observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+
+ // Make sure we're not deleted while still inside ::Observe()
+ RefPtr<PeerConnectionCtxObserver> kungFuDeathGrip(this);
+ PeerConnectionCtx::gPeerConnectionCtxObserver = nullptr;
+ }
+ if (strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) == 0) {
+ if (NS_strcmp(aData, u"" NS_IOSERVICE_OFFLINE) == 0) {
+ CSFLogDebug(LOGTAG, "Updating network state to offline");
+ PeerConnectionCtx::UpdateNetworkState(false);
+ } else if (NS_strcmp(aData, u"" NS_IOSERVICE_ONLINE) == 0) {
+ CSFLogDebug(LOGTAG, "Updating network state to online");
+ PeerConnectionCtx::UpdateNetworkState(true);
+ } else {
+ CSFLogDebug(LOGTAG, "Received unsupported network state event");
+ MOZ_CRASH();
+ }
+ }
+ return NS_OK;
+ }
+
+ private:
+ virtual ~PeerConnectionCtxObserver() {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+ }
+};
+
+NS_IMPL_ISUPPORTS(PeerConnectionCtxObserver, nsIObserver);
+} // namespace mozilla
+
+namespace mozilla {
+
+PeerConnectionCtx* PeerConnectionCtx::gInstance;
+nsIThread* PeerConnectionCtx::gMainThread;
+StaticRefPtr<PeerConnectionCtxObserver>
+ PeerConnectionCtx::gPeerConnectionCtxObserver;
+
+const std::map<const std::string, PeerConnectionImpl*>&
+PeerConnectionCtx::GetPeerConnections() {
+ return mPeerConnections;
+}
+
+nsresult PeerConnectionCtx::InitializeGlobal(nsIThread* mainThread,
+ nsISerialEventTarget* stsThread) {
+ if (!gMainThread) {
+ gMainThread = mainThread;
+ } else {
+ MOZ_ASSERT(gMainThread == mainThread);
+ }
+
+ nsresult res;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gInstance) {
+ CSFLogDebug(LOGTAG, "Creating PeerConnectionCtx");
+ PeerConnectionCtx* ctx = new PeerConnectionCtx();
+
+ res = ctx->Initialize();
+ PR_ASSERT(NS_SUCCEEDED(res));
+ if (!NS_SUCCEEDED(res)) return res;
+
+ gInstance = ctx;
+
+ if (!PeerConnectionCtx::gPeerConnectionCtxObserver) {
+ PeerConnectionCtx::gPeerConnectionCtxObserver =
+ new PeerConnectionCtxObserver();
+ PeerConnectionCtx::gPeerConnectionCtxObserver->Init();
+ }
+ }
+
+ EnableWebRtcLog();
+ return NS_OK;
+}
+
+PeerConnectionCtx* PeerConnectionCtx::GetInstance() {
+ MOZ_ASSERT(gInstance);
+ return gInstance;
+}
+
+bool PeerConnectionCtx::isActive() { return gInstance; }
+
+void PeerConnectionCtx::Destroy() {
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+
+ if (gInstance) {
+ gInstance->Cleanup();
+ delete gInstance;
+ gInstance = nullptr;
+ }
+
+ StopWebRtcLog();
+}
+
+template <typename T>
+static void RecordCommonRtpTelemetry(const T& list, const T& lastList,
+ const bool isRemote) {
+ using namespace Telemetry;
+ for (const auto& s : list) {
+ const bool isAudio = s.mKind.Value().Find("audio") != -1;
+ if (s.mPacketsLost.WasPassed() && s.mPacketsReceived.WasPassed()) {
+ if (const uint64_t total =
+ s.mPacketsLost.Value() + s.mPacketsReceived.Value()) {
+ HistogramID id =
+ isRemote ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS_RATE
+ : WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS_RATE)
+ : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS_RATE
+ : WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS_RATE);
+ Accumulate(id, (s.mPacketsLost.Value() * 1000) / total);
+ }
+ }
+ if (s.mJitter.WasPassed()) {
+ HistogramID id = isRemote
+ ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER
+ : WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER)
+ : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER
+ : WEBRTC_VIDEO_QUALITY_INBOUND_JITTER);
+ Accumulate(id, s.mJitter.Value() * 1000);
+ }
+ // Record bandwidth telemetry
+ if (s.mBytesReceived.WasPassed()) {
+ for (const auto& lastS : lastList) {
+ if (lastS.mId == s.mId) {
+ int32_t deltaMs = s.mTimestamp.Value() - lastS.mTimestamp.Value();
+ // In theory we're called every second, so delta *should* be in that
+ // range. Small deltas could cause errors due to division
+ if (deltaMs < 500 || deltaMs > 60000 ||
+ !lastS.mBytesReceived.WasPassed()) {
+ break;
+ }
+ HistogramID id =
+ isRemote
+ ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS
+ : WEBRTC_VIDEO_QUALITY_OUTBOUND_BANDWIDTH_KBITS)
+ : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS
+ : WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS);
+ // We could accumulate values until enough time has passed
+ // and then Accumulate() but this isn't that important
+ Accumulate(
+ id,
+ ((s.mBytesReceived.Value() - lastS.mBytesReceived.Value()) * 8) /
+ deltaMs);
+ break;
+ }
+ }
+ }
+ }
+}
+
+// Telemetry reporting every second after start of first call.
+// The threading model around the media pipelines is weird:
+// - The pipelines are containers,
+// - containers that are only safe on main thread, with members only safe on
+// STS,
+// - hence the there and back again approach.
+
+void PeerConnectionCtx::DeliverStats(
+ UniquePtr<dom::RTCStatsReportInternal>&& aReport) {
+ using namespace Telemetry;
+
+ // First, get reports from a second ago, if any, for calculations below
+ UniquePtr<dom::RTCStatsReportInternal> lastReport;
+ {
+ auto i = mLastReports.find(aReport->mPcid);
+ if (i != mLastReports.end()) {
+ lastReport = std::move(i->second);
+ } else {
+ lastReport = MakeUnique<dom::RTCStatsReportInternal>();
+ }
+ }
+ // Record Telemetery
+ RecordCommonRtpTelemetry(aReport->mInboundRtpStreamStats,
+ lastReport->mInboundRtpStreamStats, false);
+ RecordCommonRtpTelemetry(aReport->mRemoteInboundRtpStreamStats,
+ lastReport->mRemoteInboundRtpStreamStats, true);
+ for (const auto& s : aReport->mRemoteInboundRtpStreamStats) {
+ if (s.mRoundTripTime.WasPassed()) {
+ const bool isAudio = s.mKind.Value().Find("audio") != -1;
+ HistogramID id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_RTT
+ : WEBRTC_VIDEO_QUALITY_OUTBOUND_RTT;
+ Accumulate(id, s.mRoundTripTime.Value() * 1000);
+ }
+ }
+
+ mLastReports[aReport->mPcid] = std::move(aReport);
+}
+
+void PeerConnectionCtx::EverySecondTelemetryCallback_m(nsITimer* timer,
+ void* closure) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(PeerConnectionCtx::isActive());
+
+ for (auto& idAndPc : GetInstance()->mPeerConnections) {
+ if (idAndPc.second->HasMedia()) {
+ idAndPc.second->GetStats(nullptr, true)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [=](UniquePtr<dom::RTCStatsReportInternal>&& aReport) {
+ if (PeerConnectionCtx::isActive()) {
+ PeerConnectionCtx::GetInstance()->DeliverStats(
+ std::move(aReport));
+ }
+ },
+ [=](nsresult aError) {});
+ idAndPc.second->RecordConduitTelemetry();
+ }
+ }
+}
+
+void PeerConnectionCtx::UpdateNetworkState(bool online) {
+ auto ctx = GetInstance();
+ if (ctx->mPeerConnections.empty()) {
+ return;
+ }
+ for (auto pc : ctx->mPeerConnections) {
+ pc.second->UpdateNetworkState(online);
+ }
+}
+
+nsresult PeerConnectionCtx::Initialize() {
+ initGMP();
+
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTelemetryTimer), EverySecondTelemetryCallback_m, this,
+ 1000, nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
+ "EverySecondTelemetryCallback_m");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (XRE_IsContentProcess()) {
+ WebrtcGlobalChild::Create();
+ }
+
+ return NS_OK;
+}
+
+static void GMPReady_m() {
+ if (PeerConnectionCtx::isActive()) {
+ PeerConnectionCtx::GetInstance()->onGMPReady();
+ }
+};
+
+static void GMPReady() {
+ PeerConnectionCtx::gMainThread->Dispatch(WrapRunnableNM(&GMPReady_m),
+ NS_DISPATCH_NORMAL);
+};
+
+void PeerConnectionCtx::initGMP() {
+ mGMPService = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+
+ if (!mGMPService) {
+ CSFLogError(LOGTAG, "%s failed to get the gecko-media-plugin-service",
+ __FUNCTION__);
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = mGMPService->GetThread(getter_AddRefs(thread));
+
+ if (NS_FAILED(rv)) {
+ mGMPService = nullptr;
+ CSFLogError(LOGTAG,
+ "%s failed to get the gecko-media-plugin thread, err=%u",
+ __FUNCTION__, static_cast<unsigned>(rv));
+ return;
+ }
+
+ // presumes that all GMP dir scans have been queued for the GMPThread
+ thread->Dispatch(WrapRunnableNM(&GMPReady), NS_DISPATCH_NORMAL);
+}
+
+nsresult PeerConnectionCtx::Cleanup() {
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+
+ mQueuedJSEPOperations.Clear();
+ mGMPService = nullptr;
+ mTransportHandler = nullptr;
+ return NS_OK;
+}
+
+PeerConnectionCtx::~PeerConnectionCtx() {
+ // ensure mTelemetryTimer ends on main thread
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mTelemetryTimer) {
+ mTelemetryTimer->Cancel();
+ }
+};
+
+void PeerConnectionCtx::queueJSEPOperation(nsIRunnable* aOperation) {
+ mQueuedJSEPOperations.AppendElement(aOperation);
+}
+
+void PeerConnectionCtx::onGMPReady() {
+ mGMPReady = true;
+ for (size_t i = 0; i < mQueuedJSEPOperations.Length(); ++i) {
+ mQueuedJSEPOperations[i]->Run();
+ }
+ mQueuedJSEPOperations.Clear();
+}
+
+bool PeerConnectionCtx::gmpHasH264() {
+ if (!mGMPService) {
+ return false;
+ }
+
+ // XXX I'd prefer if this was all known ahead of time...
+
+ nsTArray<nsCString> tags;
+ tags.AppendElement("h264"_ns);
+
+ bool has_gmp;
+ nsresult rv;
+ rv = mGMPService->HasPluginForAPI(nsLiteralCString(GMP_API_VIDEO_ENCODER),
+ &tags, &has_gmp);
+ if (NS_FAILED(rv) || !has_gmp) {
+ return false;
+ }
+
+ rv = mGMPService->HasPluginForAPI(nsLiteralCString(GMP_API_VIDEO_DECODER),
+ &tags, &has_gmp);
+ if (NS_FAILED(rv) || !has_gmp) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/PeerConnectionCtx.h b/dom/media/webrtc/jsapi/PeerConnectionCtx.h
new file mode 100644
index 0000000000..098b39e1fe
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PeerConnectionCtx.h
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef peerconnectionctx_h___h__
+#define peerconnectionctx_h___h__
+
+#include <string>
+#include <map>
+
+#include "WebrtcGlobalChild.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPtr.h"
+#include "PeerConnectionImpl.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsIRunnable.h"
+#include "MediaTransportHandler.h" // Mostly for IceLogPromise
+
+namespace mozilla {
+class PeerConnectionCtxObserver;
+
+namespace dom {
+class WebrtcGlobalInformation;
+}
+
+// A class to hold some of the singleton objects we need:
+// * The global PeerConnectionImpl table and its associated lock.
+// * Stats report objects for PCs that are gone
+// * GMP related state
+class PeerConnectionCtx {
+ public:
+ static nsresult InitializeGlobal(nsIThread* mainThread,
+ nsISerialEventTarget* stsThread);
+ static PeerConnectionCtx* GetInstance();
+ static bool isActive();
+ static void Destroy();
+
+ bool isReady() {
+ // If mGMPService is not set, we aren't using GMP.
+ if (mGMPService) {
+ return mGMPReady;
+ }
+ return true;
+ }
+
+ void queueJSEPOperation(nsIRunnable* aJSEPOperation);
+ void onGMPReady();
+
+ bool gmpHasH264();
+
+ static void UpdateNetworkState(bool online);
+
+ RefPtr<MediaTransportHandler> GetTransportHandler() const {
+ return mTransportHandler;
+ }
+
+ // Make these classes friend so that they can access mPeerconnections.
+ friend class PeerConnectionImpl;
+ friend class PeerConnectionWrapper;
+ friend class mozilla::dom::WebrtcGlobalInformation;
+
+ // WebrtcGlobalInformation uses this; we put it here so we don't need to
+ // create another shutdown observer class.
+ mozilla::dom::Sequence<mozilla::dom::RTCStatsReportInternal>
+ mStatsForClosedPeerConnections;
+
+ const std::map<const std::string, PeerConnectionImpl*>& GetPeerConnections();
+
+ private:
+ // We could make these available only via accessors but it's too much trouble.
+ std::map<const std::string, PeerConnectionImpl*> mPeerConnections;
+
+ PeerConnectionCtx()
+ : mGMPReady(false),
+ mTransportHandler(
+ MediaTransportHandler::Create(GetMainThreadSerialEventTarget())) {}
+ // This is a singleton, so don't copy construct it, etc.
+ PeerConnectionCtx(const PeerConnectionCtx& other) = delete;
+ void operator=(const PeerConnectionCtx& other) = delete;
+ virtual ~PeerConnectionCtx();
+
+ nsresult Initialize();
+ nsresult Cleanup();
+
+ void initGMP();
+
+ static void EverySecondTelemetryCallback_m(nsITimer* timer, void*);
+
+ nsCOMPtr<nsITimer> mTelemetryTimer;
+
+ private:
+ void DeliverStats(UniquePtr<dom::RTCStatsReportInternal>&& aReport);
+
+ std::map<nsString, UniquePtr<dom::RTCStatsReportInternal>> mLastReports;
+ // We cannot form offers/answers properly until the Gecko Media Plugin stuff
+ // has been initted, which is a complicated mess of thread dispatches,
+ // including sync dispatches to main. So, we need to be able to queue up
+ // offer creation (or SetRemote, when we're the answerer) until all of this is
+ // ready to go, since blocking on this init is just begging for deadlock.
+ nsCOMPtr<mozIGeckoMediaPluginService> mGMPService;
+ bool mGMPReady;
+ nsTArray<nsCOMPtr<nsIRunnable>> mQueuedJSEPOperations;
+
+ // Not initted, just for ICE logging stuff
+ RefPtr<MediaTransportHandler> mTransportHandler;
+
+ static PeerConnectionCtx* gInstance;
+
+ public:
+ static nsIThread* gMainThread;
+ static mozilla::StaticRefPtr<mozilla::PeerConnectionCtxObserver>
+ gPeerConnectionCtxObserver;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp
new file mode 100644
index 0000000000..318ab64622
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp
@@ -0,0 +1,3019 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 <cstdlib>
+#include <cerrno>
+#include <deque>
+#include <set>
+#include <sstream>
+#include <vector>
+
+#include "common/browser_logging/CSFLog.h"
+#include "base/histogram.h"
+#include "common/time_profiling/timecard.h"
+
+#include "jsapi.h"
+#include "nspr.h"
+#include "nss.h"
+#include "pk11pub.h"
+
+#include "nsNetCID.h"
+#include "nsIIDNService.h"
+#include "nsILoadContext.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsProxyRelease.h"
+#include "prtime.h"
+
+#include "libwebrtcglue/AudioConduit.h"
+#include "libwebrtcglue/VideoConduit.h"
+#include "MediaTrackGraph.h"
+#include "transport/runnable_utils.h"
+#include "IPeerConnection.h"
+#include "PeerConnectionCtx.h"
+#include "PeerConnectionImpl.h"
+#include "PeerConnectionMedia.h"
+#include "RemoteTrackSource.h"
+#include "nsDOMDataChannelDeclarations.h"
+#include "transport/dtlsidentity.h"
+#include "sdp/SdpAttribute.h"
+
+#include "jsep/JsepTrack.h"
+#include "jsep/JsepSession.h"
+#include "jsep/JsepSessionImpl.h"
+
+#include "transportbridge/MediaPipeline.h"
+#include "jsapi/RTCRtpReceiver.h"
+
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Sprintf.h"
+
+#ifdef XP_WIN
+// We need to undef the MS macro for Document::CreateEvent
+# ifdef CreateEvent
+# undef CreateEvent
+# endif
+#endif // XP_WIN
+
+#include "mozilla/dom/Document.h"
+#include "nsGlobalWindow.h"
+#include "nsDOMDataChannel.h"
+#include "mozilla/dom/Location.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PublicSSL.h"
+#include "nsXULAppAPI.h"
+#include "nsContentUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsPrintfCString.h"
+#include "nsURLHelper.h"
+#include "nsNetUtil.h"
+#include "js/ArrayBuffer.h" // JS::NewArrayBufferWithContents
+#include "js/GCAnnotations.h" // JS_HAZ_ROOTED
+#include "js/RootingAPI.h" // JS::{{,Mutable}Handle,Rooted}
+#include "mozilla/PeerIdentity.h"
+#include "mozilla/dom/RTCCertificate.h"
+#include "mozilla/dom/RTCRtpReceiverBinding.h"
+#include "mozilla/dom/RTCRtpSenderBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+#include "mozilla/dom/PeerConnectionImplBinding.h"
+#include "mozilla/dom/RTCDataChannelBinding.h"
+#include "mozilla/dom/PluginCrashedEvent.h"
+#include "MediaStreamTrack.h"
+#include "AudioStreamTrack.h"
+#include "VideoStreamTrack.h"
+#include "nsIScriptGlobalObject.h"
+#include "DOMMediaStream.h"
+#include "WebrtcGlobalInformation.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/net/DataChannelProtocol.h"
+#include "MediaManager.h"
+
+#ifdef XP_WIN
+// We need to undef the MS macro again in case the windows include file
+// got imported after we included mozilla/dom/Document.h
+# ifdef CreateEvent
+# undef CreateEvent
+# endif
+#endif // XP_WIN
+
+#include "MediaSegment.h"
+
+#ifdef USE_FAKE_PCOBSERVER
+# include "FakePCObserver.h"
+#else
+# include "mozilla/dom/PeerConnectionObserverBinding.h"
+#endif
+#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h"
+
+#define ICE_PARSING \
+ "In RTCConfiguration passed to RTCPeerConnection constructor"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+typedef PCObserverString ObString;
+
+static const char* pciLogTag = "PeerConnectionImpl";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG pciLogTag
+
+static mozilla::LazyLogModule logModuleInfo("signaling");
+
+// Getting exceptions back down from PCObserver is generally not harmful.
+namespace {
+// This is a terrible hack. The problem is that SuppressException is not
+// inline, and we link this file without libxul in some cases (e.g. for our test
+// setup). So we can't use ErrorResult or IgnoredErrorResult because those call
+// SuppressException... And we can't use FastErrorResult because we can't
+// include BindingUtils.h, because our linking is completely broken. Use
+// BaseErrorResult directly. Please do not let me see _anyone_ doing this
+// without really careful review from someone who knows what they are doing.
+class JSErrorResult : public binding_danger::TErrorResult<
+ binding_danger::JustAssertCleanupPolicy> {
+ public:
+ ~JSErrorResult() { SuppressException(); }
+} JS_HAZ_ROOTED;
+
+// The WrapRunnable() macros copy passed-in args and passes them to the function
+// later on the other thread. ErrorResult cannot be passed like this because it
+// disallows copy-semantics.
+//
+// This WrappableJSErrorResult hack solves this by not actually copying the
+// ErrorResult, but creating a new one instead, which works because we don't
+// care about the result.
+//
+// Since this is for JS-calls, these can only be dispatched to the main thread.
+
+class WrappableJSErrorResult {
+ public:
+ WrappableJSErrorResult() : mRv(MakeUnique<JSErrorResult>()), isCopy(false) {}
+ WrappableJSErrorResult(const WrappableJSErrorResult& other)
+ : mRv(MakeUnique<JSErrorResult>()), isCopy(true) {}
+ ~WrappableJSErrorResult() {
+ if (isCopy) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+ }
+ operator ErrorResult&() { return *mRv; }
+
+ private:
+ mozilla::UniquePtr<JSErrorResult> mRv;
+ bool isCopy;
+} JS_HAZ_ROOTED;
+
+} // namespace
+
+static nsresult InitNSSInContent() {
+ NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
+
+ if (!XRE_IsContentProcess()) {
+ MOZ_ASSERT_UNREACHABLE("Must be called in content process");
+ return NS_ERROR_FAILURE;
+ }
+
+ static bool nssStarted = false;
+ if (nssStarted) {
+ return NS_OK;
+ }
+
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ CSFLogError(LOGTAG, "NSS_NoDB_Init failed.");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) {
+ CSFLogError(LOGTAG, "Fail to set up nss cipher suite.");
+ return NS_ERROR_FAILURE;
+ }
+
+ mozilla::psm::DisableMD5();
+
+ nssStarted = true;
+
+ return NS_OK;
+}
+
+namespace mozilla {
+class DataChannel;
+}
+
+namespace mozilla {
+
+void PeerConnectionAutoTimer::RegisterConnection() { mRefCnt++; }
+
+void PeerConnectionAutoTimer::UnregisterConnection() {
+ MOZ_ASSERT(mRefCnt);
+ mRefCnt--;
+ if (mRefCnt == 0) {
+ Telemetry::Accumulate(
+ Telemetry::WEBRTC_CALL_DURATION,
+ static_cast<uint32_t>((TimeStamp::Now() - mStart).ToSeconds()));
+ }
+}
+
+bool PeerConnectionAutoTimer::IsStopped() { return mRefCnt == 0; }
+
+NS_IMPL_ISUPPORTS0(PeerConnectionImpl)
+
+already_AddRefed<PeerConnectionImpl> PeerConnectionImpl::Constructor(
+ const dom::GlobalObject& aGlobal) {
+ RefPtr<PeerConnectionImpl> pc = new PeerConnectionImpl(&aGlobal);
+
+ CSFLogDebug(LOGTAG, "Created PeerConnection: %p", pc.get());
+
+ return pc.forget();
+}
+
+bool PeerConnectionImpl::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return PeerConnectionImpl_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+bool PCUuidGenerator::Generate(std::string* idp) {
+ nsresult rv;
+
+ if (!mGenerator) {
+ mGenerator = do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ if (!mGenerator) {
+ return false;
+ }
+ }
+
+ nsID id;
+ rv = mGenerator->GenerateUUIDInPlace(&id);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ char buffer[NSID_LENGTH];
+ id.ToProvidedString(buffer);
+ idp->assign(buffer);
+
+ return true;
+}
+
+bool IsPrivateBrowsing(nsPIDOMWindowInner* aWindow) {
+ if (!aWindow) {
+ return false;
+ }
+
+ Document* doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ return false;
+ }
+
+ nsILoadContext* loadContext = doc->GetLoadContext();
+ return loadContext && loadContext->UsePrivateBrowsing();
+}
+
+PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal)
+ : mTimeCard(MOZ_LOG_TEST(logModuleInfo, LogLevel::Error) ? create_timecard()
+ : nullptr),
+ mJsConfiguration(),
+ mSignalingState(RTCSignalingState::Stable),
+ mIceConnectionState(RTCIceConnectionState::New),
+ mIceGatheringState(RTCIceGatheringState::New),
+ mWindow(nullptr),
+ mCertificate(nullptr),
+ mSTSThread(nullptr),
+ mForceIceTcp(false),
+ mMedia(nullptr),
+ mTransportHandler(nullptr),
+ mUuidGen(MakeUnique<PCUuidGenerator>()),
+ mIceRestartCount(0),
+ mIceRollbackCount(0),
+ mHaveConfiguredCodecs(false),
+ mTrickle(true) // TODO(ekr@rtfm.com): Use pref
+ ,
+ mPrivateWindow(false),
+ mActiveOnWindow(false),
+ mPacketDumpEnabled(false),
+ mPacketDumpFlagsMutex("Packet dump flags mutex"),
+ mTimestampMaker(aGlobal),
+ mIdGenerator(new RTCStatsIdGenerator()),
+ listenPort(0),
+ connectPort(0),
+ connectStr(nullptr) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aGlobal) {
+ mWindow = do_QueryInterface(aGlobal->GetAsSupports());
+ if (IsPrivateBrowsing(mWindow)) {
+ mPrivateWindow = true;
+ }
+ mWindow->AddPeerConnection();
+ mActiveOnWindow = true;
+
+ mRtxIsAllowed =
+ !HostnameInPref("media.peerconnection.video.use_rtx.blocklist",
+ mWindow->GetDocumentURI());
+ }
+ CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl constructor for %s", __FUNCTION__,
+ mHandle.c_str());
+ STAMP_TIMECARD(mTimeCard, "Constructor Completed");
+ mForceIceTcp =
+ Preferences::GetBool("media.peerconnection.ice.force_ice_tcp", false);
+ memset(mMaxReceiving, 0, sizeof(mMaxReceiving));
+ memset(mMaxSending, 0, sizeof(mMaxSending));
+}
+
+PeerConnectionImpl::~PeerConnectionImpl() {
+ if (mTimeCard) {
+ STAMP_TIMECARD(mTimeCard, "Destructor Invoked");
+ print_timecard(mTimeCard);
+ destroy_timecard(mTimeCard);
+ mTimeCard = nullptr;
+ }
+ // This aborts if not on main thread (in Debug builds)
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ if (mWindow && mActiveOnWindow) {
+ mWindow->RemovePeerConnection();
+ // No code is supposed to observe the assignment below, but
+ // hopefully it makes looking at this object in a debugger
+ // make more sense.
+ mActiveOnWindow = false;
+ }
+
+ if (mPrivateWindow && mTransportHandler) {
+ mTransportHandler->ExitPrivateMode();
+ }
+ if (PeerConnectionCtx::isActive()) {
+ PeerConnectionCtx::GetInstance()->mPeerConnections.erase(mHandle);
+ } else {
+ CSFLogError(LOGTAG, "PeerConnectionCtx is already gone. Ignoring...");
+ }
+
+ CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl destructor invoked for %s",
+ __FUNCTION__, mHandle.c_str());
+
+ // Since this and Initialize() occur on MainThread, they can't both be
+ // running at once
+
+ // 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
+ ShutdownMedia();
+}
+
+nsresult PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindowInner* aWindow,
+ const RTCConfiguration& aConfiguration,
+ nsISupports* aThread) {
+ nsresult res;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aThread);
+ if (!mThread) {
+ mThread = do_QueryInterface(aThread);
+ MOZ_ASSERT(mThread);
+ }
+ CheckThread();
+
+ // Store the configuration for about:webrtc
+ StoreConfigurationForAboutWebrtc(aConfiguration);
+
+ mPCObserver = &aObserver;
+
+ // Find the STS thread
+
+ mSTSThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
+ MOZ_ASSERT(mSTSThread);
+
+ // We do callback handling on STS instead of main to avoid media jank.
+ // Someday, we may have a dedicated thread for this.
+ mTransportHandler = MediaTransportHandler::Create(mSTSThread);
+ if (mPrivateWindow) {
+ mTransportHandler->EnterPrivateMode();
+ }
+
+ // Initialize NSS if we are in content process. For chrome process, NSS should
+ // already been initialized.
+ if (XRE_IsParentProcess()) {
+ // This code interferes with the C++ unit test startup code.
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &res);
+ NS_ENSURE_SUCCESS(res, res);
+ } else {
+ NS_ENSURE_SUCCESS(res = InitNSSInContent(), res);
+ }
+
+ // Currently no standalone unit tests for DataChannel,
+ // which is the user of mWindow
+ MOZ_ASSERT(aWindow);
+ mWindow = aWindow;
+ NS_ENSURE_STATE(mWindow);
+
+ PRTime timestamp = PR_Now();
+ // Ok if we truncate this.
+ char temp[128];
+
+ nsAutoCString locationCStr;
+
+ RefPtr<Location> location = mWindow->Location();
+ nsAutoString locationAStr;
+ res = location->ToString(locationAStr);
+ NS_ENSURE_SUCCESS(res, res);
+
+ CopyUTF16toUTF8(locationAStr, locationCStr);
+
+ if (!mUuidGen->Generate(&mHandle)) {
+ MOZ_CRASH();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ SprintfLiteral(temp, "%s %" PRIu64 " (id=%" PRIu64 " url=%s)",
+ mHandle.c_str(), static_cast<uint64_t>(timestamp),
+ static_cast<uint64_t>(mWindow ? mWindow->WindowID() : 0),
+ locationCStr.get() ? locationCStr.get() : "NULL");
+
+ mName = temp;
+
+ STAMP_TIMECARD(mTimeCard, "Initializing PC Ctx");
+ res = PeerConnectionCtx::InitializeGlobal(mThread, mSTSThread);
+ NS_ENSURE_SUCCESS(res, res);
+
+ nsTArray<dom::RTCIceServer> iceServers;
+ if (aConfiguration.mIceServers.WasPassed()) {
+ iceServers = aConfiguration.mIceServers.Value();
+ }
+
+ res = mTransportHandler->CreateIceCtx("PC:" + GetName(), iceServers,
+ aConfiguration.mIceTransportPolicy);
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG, "%s: Failed to init mtransport", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ mJsepSession =
+ MakeUnique<JsepSessionImpl>(mName, MakeUnique<PCUuidGenerator>());
+ mJsepSession->SetRtxIsAllowed(mRtxIsAllowed);
+
+ res = mJsepSession->Init();
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG, "%s: Couldn't init JSEP Session, res=%u", __FUNCTION__,
+ static_cast<unsigned>(res));
+ return res;
+ }
+
+ JsepBundlePolicy bundlePolicy;
+ switch (aConfiguration.mBundlePolicy) {
+ case dom::RTCBundlePolicy::Balanced:
+ bundlePolicy = kBundleBalanced;
+ break;
+ case dom::RTCBundlePolicy::Max_compat:
+ bundlePolicy = kBundleMaxCompat;
+ break;
+ case dom::RTCBundlePolicy::Max_bundle:
+ bundlePolicy = kBundleMaxBundle;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+
+ res = mJsepSession->SetBundlePolicy(bundlePolicy);
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG, "%s: Couldn't set bundle policy, res=%u, error=%s",
+ __FUNCTION__, static_cast<unsigned>(res),
+ mJsepSession->GetLastError().c_str());
+ return res;
+ }
+
+ mMedia = new PeerConnectionMedia(this);
+
+ // Initialize the media object.
+ res = mMedia->Init();
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG, "%s: Couldn't initialize media object", __FUNCTION__);
+ ShutdownMedia();
+ return res;
+ }
+
+ PeerConnectionCtx::GetInstance()->mPeerConnections[mHandle] = this;
+
+ return NS_OK;
+}
+
+void PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindowInner& aWindow,
+ const RTCConfiguration& aConfiguration,
+ nsISupports* aThread, ErrorResult& rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aThread);
+ mThread = do_QueryInterface(aThread);
+
+ nsresult res = Initialize(aObserver, &aWindow, aConfiguration, aThread);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return;
+ }
+
+ if (!aConfiguration.mPeerIdentity.IsEmpty()) {
+ mPeerIdentity = new PeerIdentity(aConfiguration.mPeerIdentity);
+ mPrivacyRequested = Some(true);
+ }
+}
+
+void PeerConnectionImpl::SetCertificate(
+ mozilla::dom::RTCCertificate& aCertificate) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(!mCertificate, "This can only be called once");
+ mCertificate = &aCertificate;
+
+ std::vector<uint8_t> fingerprint;
+ nsresult rv =
+ CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fingerprint);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: Couldn't calculate fingerprint, rv=%u",
+ __FUNCTION__, static_cast<unsigned>(rv));
+ mCertificate = nullptr;
+ return;
+ }
+ rv = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM,
+ fingerprint);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: Couldn't set DTLS credentials, rv=%u",
+ __FUNCTION__, static_cast<unsigned>(rv));
+ mCertificate = nullptr;
+ }
+}
+
+const RefPtr<mozilla::dom::RTCCertificate>& PeerConnectionImpl::Certificate()
+ const {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mCertificate;
+}
+
+RefPtr<DtlsIdentity> PeerConnectionImpl::Identity() const {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(mCertificate);
+ return mCertificate->CreateDtlsIdentity();
+}
+
+class CompareCodecPriority {
+ public:
+ void SetPreferredCodec(int32_t preferredCodec) {
+ // This pref really ought to be a string, preferably something like
+ // "H264" or "VP8" instead of a payload type.
+ // Bug 1101259.
+ std::ostringstream os;
+ os << preferredCodec;
+ mPreferredCodec = os.str();
+ }
+
+ bool operator()(const UniquePtr<JsepCodecDescription>& lhs,
+ const UniquePtr<JsepCodecDescription>& rhs) const {
+ if (!mPreferredCodec.empty() && lhs->mDefaultPt == mPreferredCodec &&
+ rhs->mDefaultPt != mPreferredCodec) {
+ return true;
+ }
+
+ if (lhs->mStronglyPreferred && !rhs->mStronglyPreferred) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private:
+ std::string mPreferredCodec;
+};
+
+class ConfigureCodec {
+ public:
+ explicit ConfigureCodec(nsCOMPtr<nsIPrefBranch>& branch)
+ : mHardwareH264Enabled(false),
+ mSoftwareH264Enabled(false),
+ mH264Enabled(false),
+ mVP9Enabled(true),
+ mVP9Preferred(false),
+ mH264Level(13), // minimum suggested for WebRTC spec
+ mH264MaxBr(0), // Unlimited
+ mH264MaxMbps(0), // Unlimited
+ mVP8MaxFs(0),
+ mVP8MaxFr(0),
+ mUseTmmbr(false),
+ mUseRemb(false),
+ mUseTransportCC(false),
+ mUseAudioFec(false),
+ mRedUlpfecEnabled(false),
+ mDtmfEnabled(false) {
+ mSoftwareH264Enabled = PeerConnectionCtx::GetInstance()->gmpHasH264();
+
+ if (WebrtcVideoConduit::HasH264Hardware()) {
+ branch->GetBoolPref("media.webrtc.hw.h264.enabled",
+ &mHardwareH264Enabled);
+ }
+
+ mH264Enabled = mHardwareH264Enabled || mSoftwareH264Enabled;
+
+ branch->GetIntPref("media.navigator.video.h264.level", &mH264Level);
+ mH264Level &= 0xFF;
+
+ branch->GetIntPref("media.navigator.video.h264.max_br", &mH264MaxBr);
+
+ branch->GetIntPref("media.navigator.video.h264.max_mbps", &mH264MaxMbps);
+
+ branch->GetBoolPref("media.peerconnection.video.vp9_enabled", &mVP9Enabled);
+
+ branch->GetBoolPref("media.peerconnection.video.vp9_preferred",
+ &mVP9Preferred);
+
+ branch->GetIntPref("media.navigator.video.max_fs", &mVP8MaxFs);
+ if (mVP8MaxFs <= 0) {
+ mVP8MaxFs = 12288; // We must specify something other than 0
+ }
+
+ branch->GetIntPref("media.navigator.video.max_fr", &mVP8MaxFr);
+ if (mVP8MaxFr <= 0) {
+ mVP8MaxFr = 60; // We must specify something other than 0
+ }
+
+ // TMMBR is enabled from a pref in about:config
+ branch->GetBoolPref("media.navigator.video.use_tmmbr", &mUseTmmbr);
+
+ // REMB is enabled by default, but can be disabled from about:config
+ branch->GetBoolPref("media.navigator.video.use_remb", &mUseRemb);
+
+ branch->GetBoolPref("media.navigator.video.use_transport_cc",
+ &mUseTransportCC);
+
+ branch->GetBoolPref("media.navigator.audio.use_fec", &mUseAudioFec);
+
+ branch->GetBoolPref("media.navigator.video.red_ulpfec_enabled",
+ &mRedUlpfecEnabled);
+
+ // media.peerconnection.dtmf.enabled controls both sdp generation for
+ // DTMF support as well as DTMF exposure to DOM
+ branch->GetBoolPref("media.peerconnection.dtmf.enabled", &mDtmfEnabled);
+ }
+
+ void operator()(UniquePtr<JsepCodecDescription>& codec) const {
+ switch (codec->mType) {
+ case SdpMediaSection::kAudio: {
+ JsepAudioCodecDescription& audioCodec =
+ static_cast<JsepAudioCodecDescription&>(*codec);
+ if (audioCodec.mName == "opus") {
+ audioCodec.mFECEnabled = mUseAudioFec;
+ } else if (audioCodec.mName == "telephone-event") {
+ audioCodec.mEnabled = mDtmfEnabled;
+ }
+ } break;
+ case SdpMediaSection::kVideo: {
+ JsepVideoCodecDescription& videoCodec =
+ static_cast<JsepVideoCodecDescription&>(*codec);
+
+ if (videoCodec.mName == "H264") {
+ // Override level
+ videoCodec.mProfileLevelId &= 0xFFFF00;
+ videoCodec.mProfileLevelId |= mH264Level;
+
+ videoCodec.mConstraints.maxBr = mH264MaxBr;
+
+ videoCodec.mConstraints.maxMbps = mH264MaxMbps;
+
+ // Might disable it, but we set up other params anyway
+ videoCodec.mEnabled = mH264Enabled;
+
+ if (videoCodec.mPacketizationMode == 0 && !mSoftwareH264Enabled) {
+ // We're assuming packetization mode 0 is unsupported by
+ // hardware.
+ videoCodec.mEnabled = false;
+ }
+
+ if (mHardwareH264Enabled) {
+ videoCodec.mStronglyPreferred = true;
+ }
+ } else if (videoCodec.mName == "red") {
+ videoCodec.mEnabled = mRedUlpfecEnabled;
+ } else if (videoCodec.mName == "ulpfec") {
+ videoCodec.mEnabled = mRedUlpfecEnabled;
+ } else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") {
+ if (videoCodec.mName == "VP9") {
+ if (!mVP9Enabled) {
+ videoCodec.mEnabled = false;
+ break;
+ }
+ if (mVP9Preferred) {
+ videoCodec.mStronglyPreferred = true;
+ }
+ }
+ videoCodec.mConstraints.maxFs = mVP8MaxFs;
+ videoCodec.mConstraints.maxFps = mVP8MaxFr;
+ }
+
+ if (mUseTmmbr) {
+ videoCodec.EnableTmmbr();
+ }
+ if (mUseRemb) {
+ videoCodec.EnableRemb();
+ }
+ if (mUseTransportCC) {
+ videoCodec.EnableTransportCC();
+ }
+ } break;
+ case SdpMediaSection::kText:
+ case SdpMediaSection::kApplication:
+ case SdpMediaSection::kMessage: {
+ } // Nothing to configure for these.
+ }
+ }
+
+ private:
+ bool mHardwareH264Enabled;
+ bool mSoftwareH264Enabled;
+ bool mH264Enabled;
+ bool mVP9Enabled;
+ bool mVP9Preferred;
+ int32_t mH264Level;
+ int32_t mH264MaxBr;
+ int32_t mH264MaxMbps;
+ int32_t mVP8MaxFs;
+ int32_t mVP8MaxFr;
+ bool mUseTmmbr;
+ bool mUseRemb;
+ bool mUseTransportCC;
+ bool mUseAudioFec;
+ bool mRedUlpfecEnabled;
+ bool mDtmfEnabled;
+};
+
+class ConfigureRedCodec {
+ public:
+ explicit ConfigureRedCodec(nsCOMPtr<nsIPrefBranch>& branch,
+ std::vector<uint8_t>* redundantEncodings)
+ : mRedundantEncodings(redundantEncodings) {
+ // if we wanted to override or modify which encodings are considered
+ // for redundant encodings, we'd probably want to handle it here by
+ // checking prefs modifying the operator() code below
+ }
+
+ void operator()(UniquePtr<JsepCodecDescription>& codec) const {
+ if (codec->mType == SdpMediaSection::kVideo && codec->mEnabled == false) {
+ uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10);
+ // don't search for the codec payload type unless we have a valid
+ // conversion (non-zero)
+ if (pt != 0) {
+ std::vector<uint8_t>::iterator it = std::find(
+ mRedundantEncodings->begin(), mRedundantEncodings->end(), pt);
+ if (it != mRedundantEncodings->end()) {
+ mRedundantEncodings->erase(it);
+ }
+ }
+ }
+ }
+
+ private:
+ std::vector<uint8_t>* mRedundantEncodings;
+};
+
+nsresult PeerConnectionImpl::ConfigureJsepSessionCodecs() {
+ nsresult res;
+ nsCOMPtr<nsIPrefService> prefs =
+ do_GetService("@mozilla.org/preferences-service;1", &res);
+
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG, "%s: Couldn't get prefs service, res=%u", __FUNCTION__,
+ static_cast<unsigned>(res));
+ return res;
+ }
+
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
+ if (!branch) {
+ CSFLogError(LOGTAG, "%s: Couldn't get prefs branch", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ ConfigureCodec configurer(branch);
+ mJsepSession->ForEachCodec(configurer);
+
+ // if red codec is enabled, configure it for the other enabled codecs
+ for (auto& codec : mJsepSession->Codecs()) {
+ if (codec->mName == "red" && codec->mEnabled) {
+ JsepVideoCodecDescription* redCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ ConfigureRedCodec configureRed(branch, &(redCodec->mRedundantEncodings));
+ mJsepSession->ForEachCodec(configureRed);
+ break;
+ }
+ }
+
+ // We use this to sort the list of codecs once everything is configured
+ CompareCodecPriority comparator;
+
+ // Sort by priority
+ int32_t preferredCodec = 0;
+ branch->GetIntPref("media.navigator.video.preferred_codec", &preferredCodec);
+
+ if (preferredCodec) {
+ comparator.SetPreferredCodec(preferredCodec);
+ }
+
+ mJsepSession->SortCodecs(comparator);
+ return NS_OK;
+}
+
+// Data channels won't work without a window, so in order for the C++ unit
+// tests to work (it doesn't have a window available) we ifdef the following
+// two implementations.
+//
+// Note: 'media.peerconnection.sctp.force_maximum_message_size' changes
+// behaviour triggered by these parameters.
+NS_IMETHODIMP
+PeerConnectionImpl::EnsureDataConnection(uint16_t aLocalPort,
+ uint16_t aNumstreams,
+ uint32_t aMaxMessageSize,
+ bool aMMSSet) {
+ PC_AUTO_ENTER_API_CALL(false);
+
+ if (mDataConnection) {
+ CSFLogDebug(LOGTAG, "%s DataConnection already connected", __FUNCTION__);
+ mDataConnection->SetMaxMessageSize(aMMSSet, aMaxMessageSize);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISerialEventTarget> target =
+ mWindow ? mWindow->EventTargetFor(TaskCategory::Other) : nullptr;
+ Maybe<uint64_t> mms = aMMSSet ? Some(aMaxMessageSize) : Nothing();
+ if (auto res = DataChannelConnection::Create(this, target, mTransportHandler,
+ aLocalPort, aNumstreams, mms)) {
+ mDataConnection = res.value();
+ CSFLogDebug(LOGTAG, "%s DataChannelConnection %p attached to %s",
+ __FUNCTION__, (void*)mDataConnection.get(), mHandle.c_str());
+ return NS_OK;
+ }
+ CSFLogError(LOGTAG, "%s DataConnection Create Failed", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+}
+
+nsresult PeerConnectionImpl::GetDatachannelParameters(
+ uint32_t* channels, uint16_t* localport, uint16_t* remoteport,
+ uint32_t* remotemaxmessagesize, bool* mmsset, std::string* transportId,
+ bool* client) const {
+ // Clear, just in case we fail.
+ *channels = 0;
+ *localport = 0;
+ *remoteport = 0;
+ *remotemaxmessagesize = 0;
+ *mmsset = false;
+ transportId->clear();
+
+ RefPtr<JsepTransceiver> datachannelTransceiver;
+ for (const auto& [id, transceiver] : mJsepSession->GetTransceivers()) {
+ (void)id; // Lame, but no better way to do this right now.
+ if ((transceiver->GetMediaType() == SdpMediaSection::kApplication) &&
+ transceiver->mSendTrack.GetNegotiatedDetails()) {
+ datachannelTransceiver = transceiver;
+ break;
+ }
+ }
+
+ if (!datachannelTransceiver ||
+ !datachannelTransceiver->mTransport.mComponents) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // This will release assert if there is no such index, and that's ok
+ const JsepTrackEncoding& encoding =
+ datachannelTransceiver->mSendTrack.GetNegotiatedDetails()->GetEncoding(0);
+
+ if (NS_WARN_IF(encoding.GetCodecs().empty())) {
+ CSFLogError(LOGTAG,
+ "%s: Negotiated m=application with no codec. "
+ "This is likely to be broken.",
+ __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ for (const auto& codec : encoding.GetCodecs()) {
+ if (codec->mType != SdpMediaSection::kApplication) {
+ CSFLogError(LOGTAG,
+ "%s: Codec type for m=application was %u, this "
+ "is a bug.",
+ __FUNCTION__, static_cast<unsigned>(codec->mType));
+ MOZ_ASSERT(false, "Codec for m=application was not \"application\"");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (codec->mName != "webrtc-datachannel") {
+ CSFLogWarn(LOGTAG,
+ "%s: Codec for m=application was not "
+ "webrtc-datachannel (was instead %s). ",
+ __FUNCTION__, codec->mName.c_str());
+ continue;
+ }
+
+ if (codec->mChannels) {
+ *channels = codec->mChannels;
+ } else {
+ *channels = WEBRTC_DATACHANNEL_STREAMS_DEFAULT;
+ }
+ const JsepApplicationCodecDescription* appCodec =
+ static_cast<const JsepApplicationCodecDescription*>(codec.get());
+ *localport = appCodec->mLocalPort;
+ *remoteport = appCodec->mRemotePort;
+ *remotemaxmessagesize = appCodec->mRemoteMaxMessageSize;
+ *mmsset = appCodec->mRemoteMMSSet;
+ MOZ_ASSERT(!datachannelTransceiver->mTransport.mTransportId.empty());
+ *transportId = datachannelTransceiver->mTransport.mTransportId;
+ *client = datachannelTransceiver->mTransport.mDtls->GetRole() ==
+ JsepDtlsTransport::kJsepDtlsClient;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult PeerConnectionImpl::AddRtpTransceiverToJsepSession(
+ RefPtr<JsepTransceiver>& transceiver) {
+ nsresult res = ConfigureJsepSessionCodecs();
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG, "Failed to configure codecs");
+ return res;
+ }
+
+ res = mJsepSession->AddTransceiver(transceiver);
+
+ if (NS_FAILED(res)) {
+ std::string errorString = mJsepSession->GetLastError();
+ CSFLogError(LOGTAG, "%s (%s) : pc = %s, error = %s", __FUNCTION__,
+ transceiver->GetMediaType() == SdpMediaSection::kAudio
+ ? "audio"
+ : "video",
+ mHandle.c_str(), errorString.c_str());
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<TransceiverImpl> PeerConnectionImpl::CreateTransceiverImpl(
+ JsepTransceiver* aJsepTransceiver, dom::MediaStreamTrack* aSendTrack,
+ ErrorResult& aRv) {
+ // TODO: Maybe this should be done in PeerConnectionMedia?
+ if (aSendTrack) {
+ aSendTrack->AddPrincipalChangeObserver(this);
+ }
+
+ RefPtr<TransceiverImpl> transceiverImpl;
+
+ aRv = mMedia->AddTransceiver(aJsepTransceiver, aSendTrack, &transceiverImpl);
+
+ return transceiverImpl.forget();
+}
+
+already_AddRefed<TransceiverImpl> PeerConnectionImpl::CreateTransceiverImpl(
+ const nsAString& aKind, dom::MediaStreamTrack* aSendTrack,
+ ErrorResult& jrv) {
+ SdpMediaSection::MediaType type;
+ if (aKind.EqualsASCII("audio")) {
+ type = SdpMediaSection::MediaType::kAudio;
+ } else if (aKind.EqualsASCII("video")) {
+ type = SdpMediaSection::MediaType::kVideo;
+ } else {
+ MOZ_ASSERT(false);
+ jrv = NS_ERROR_INVALID_ARG;
+ return nullptr;
+ }
+
+ RefPtr<JsepTransceiver> jsepTransceiver = new JsepTransceiver(type);
+
+ RefPtr<TransceiverImpl> transceiverImpl =
+ CreateTransceiverImpl(jsepTransceiver, aSendTrack, jrv);
+
+ if (jrv.Failed()) {
+ // Would be nice if we could peek at the rv without stealing it, so we
+ // could log...
+ CSFLogError(LOGTAG, "%s: failed", __FUNCTION__);
+ return nullptr;
+ }
+
+ jsepTransceiver->SetRtxIsAllowed(mRtxIsAllowed);
+
+ // Do this last, since it is not possible to roll back.
+ nsresult rv = AddRtpTransceiverToJsepSession(jsepTransceiver);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: AddRtpTransceiverToJsepSession failed, res=%u",
+ __FUNCTION__, static_cast<unsigned>(rv));
+ jrv = rv;
+ return nullptr;
+ }
+
+ return transceiverImpl.forget();
+}
+
+bool PeerConnectionImpl::CheckNegotiationNeeded(ErrorResult& rv) {
+ MOZ_ASSERT(mSignalingState == RTCSignalingState::Stable);
+ return mJsepSession->CheckNegotiationNeeded();
+}
+
+nsresult PeerConnectionImpl::InitializeDataChannel() {
+ PC_AUTO_ENTER_API_CALL(false);
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+
+ uint32_t channels = 0;
+ uint16_t localport = 0;
+ uint16_t remoteport = 0;
+ uint32_t remotemaxmessagesize = 0;
+ bool mmsset = false;
+ std::string transportId;
+ bool client = false;
+ nsresult rv = GetDatachannelParameters(&channels, &localport, &remoteport,
+ &remotemaxmessagesize, &mmsset,
+ &transportId, &client);
+
+ if (NS_FAILED(rv)) {
+ CSFLogDebug(LOGTAG, "%s: We did not negotiate datachannel", __FUNCTION__);
+ return NS_OK;
+ }
+
+ if (channels > MAX_NUM_STREAMS) {
+ channels = MAX_NUM_STREAMS;
+ }
+
+ rv = EnsureDataConnection(localport, channels, remotemaxmessagesize, mmsset);
+ if (NS_SUCCEEDED(rv)) {
+ if (mDataConnection->ConnectToTransport(transportId, client, localport,
+ remoteport)) {
+ return NS_OK;
+ }
+ // If we inited the DataConnection, call Destroy() before releasing it
+ mDataConnection->Destroy();
+ }
+ mDataConnection = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+already_AddRefed<nsDOMDataChannel> PeerConnectionImpl::CreateDataChannel(
+ const nsAString& aLabel, const nsAString& aProtocol, uint16_t aType,
+ bool ordered, uint16_t aMaxTime, uint16_t aMaxNum, bool aExternalNegotiated,
+ uint16_t aStream, ErrorResult& rv) {
+ RefPtr<nsDOMDataChannel> result;
+ rv = CreateDataChannel(aLabel, aProtocol, aType, ordered, aMaxTime, aMaxNum,
+ aExternalNegotiated, aStream, getter_AddRefs(result));
+ return result.forget();
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CreateDataChannel(
+ const nsAString& aLabel, const nsAString& aProtocol, uint16_t aType,
+ bool ordered, uint16_t aMaxTime, uint16_t aMaxNum, bool aExternalNegotiated,
+ uint16_t aStream, nsDOMDataChannel** aRetval) {
+ PC_AUTO_ENTER_API_CALL(false);
+ MOZ_ASSERT(aRetval);
+
+ RefPtr<DataChannel> dataChannel;
+ DataChannelConnection::Type theType =
+ static_cast<DataChannelConnection::Type>(aType);
+
+ nsresult rv = EnsureDataConnection(
+ WEBRTC_DATACHANNEL_PORT_DEFAULT, WEBRTC_DATACHANNEL_STREAMS_DEFAULT,
+ WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE_DEFAULT, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ dataChannel = mDataConnection->Open(
+ NS_ConvertUTF16toUTF8(aLabel), NS_ConvertUTF16toUTF8(aProtocol), theType,
+ ordered,
+ aType == DataChannelConnection::PARTIAL_RELIABLE_REXMIT
+ ? aMaxNum
+ : (aType == DataChannelConnection::PARTIAL_RELIABLE_TIMED ? aMaxTime
+ : 0),
+ nullptr, nullptr, aExternalNegotiated, aStream);
+ NS_ENSURE_TRUE(dataChannel, NS_ERROR_NOT_AVAILABLE);
+
+ CSFLogDebug(LOGTAG, "%s: making DOMDataChannel", __FUNCTION__);
+
+ RefPtr<JsepTransceiver> dcTransceiver;
+ for (auto& [id, transceiver] : mJsepSession->GetTransceivers()) {
+ (void)id; // Lame, but no better way to do this right now.
+ if (transceiver->GetMediaType() == SdpMediaSection::kApplication) {
+ dcTransceiver = transceiver;
+ break;
+ }
+ }
+
+ if (!dcTransceiver) {
+ dcTransceiver =
+ new JsepTransceiver(SdpMediaSection::MediaType::kApplication);
+ mJsepSession->AddTransceiver(dcTransceiver);
+ }
+
+ dcTransceiver->RestartDatachannelTransceiver();
+
+ RefPtr<nsDOMDataChannel> retval;
+ rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow,
+ getter_AddRefs(retval));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ retval.forget(aRetval);
+ return NS_OK;
+}
+
+void PeerConnectionImpl::NotifyDataChannel(
+ already_AddRefed<DataChannel> aChannel) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ RefPtr<DataChannel> channel(aChannel);
+ MOZ_ASSERT(channel);
+ CSFLogDebug(LOGTAG, "%s: channel: %p", __FUNCTION__, channel.get());
+
+ RefPtr<nsDOMDataChannel> domchannel;
+ nsresult rv = NS_NewDOMDataChannel(channel.forget(), mWindow,
+ getter_AddRefs(domchannel));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ JSErrorResult jrv;
+ mPCObserver->NotifyDataChannel(*domchannel, jrv);
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CreateOffer(const RTCOfferOptions& aOptions) {
+ JsepOfferOptions options;
+ // convert the RTCOfferOptions to JsepOfferOptions
+ if (aOptions.mOfferToReceiveAudio.WasPassed()) {
+ options.mOfferToReceiveAudio =
+ mozilla::Some(size_t(aOptions.mOfferToReceiveAudio.Value()));
+ }
+
+ if (aOptions.mOfferToReceiveVideo.WasPassed()) {
+ options.mOfferToReceiveVideo =
+ mozilla::Some(size_t(aOptions.mOfferToReceiveVideo.Value()));
+ }
+
+ options.mIceRestart = mozilla::Some(aOptions.mIceRestart);
+
+ return CreateOffer(options);
+}
+
+static void DeferredCreateOffer(const std::string& aPcHandle,
+ const JsepOfferOptions& aOptions) {
+ PeerConnectionWrapper wrapper(aPcHandle);
+
+ if (wrapper.impl()) {
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ MOZ_CRASH(
+ "Why is DeferredCreateOffer being executed when the "
+ "PeerConnectionCtx isn't ready?");
+ }
+ wrapper.impl()->CreateOffer(aOptions);
+ }
+}
+
+// Have to use unique_ptr because webidl enums are generated without a
+// copy c'tor.
+static std::unique_ptr<dom::PCErrorData> buildJSErrorData(
+ const JsepSession::Result& aResult, const std::string& aMessage) {
+ std::unique_ptr<dom::PCErrorData> result(new dom::PCErrorData);
+ result->mName = *aResult.mError;
+ result->mMessage = NS_ConvertASCIItoUTF16(aMessage.c_str());
+ return result;
+}
+
+// Used by unit tests and the IDL CreateOffer.
+NS_IMETHODIMP
+PeerConnectionImpl::CreateOffer(const JsepOfferOptions& aOptions) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ // Uh oh. We're not ready yet. Enqueue this operation.
+ PeerConnectionCtx::GetInstance()->queueJSEPOperation(
+ WrapRunnableNM(DeferredCreateOffer, mHandle, aOptions));
+ STAMP_TIMECARD(mTimeCard, "Deferring CreateOffer (not ready)");
+ return NS_OK;
+ }
+
+ CSFLogDebug(LOGTAG, "CreateOffer()");
+
+ nsresult nrv = ConfigureJsepSessionCodecs();
+ if (NS_FAILED(nrv)) {
+ CSFLogError(LOGTAG, "Failed to configure codecs");
+ return nrv;
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Create Offer");
+
+ mThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<PeerConnectionImpl>(this), aOptions] {
+ std::string offer;
+
+ JsepSession::Result result =
+ mJsepSession->CreateOffer(aOptions, &offer);
+ JSErrorResult rv;
+ if (result.mError.isSome()) {
+ std::string errorString = mJsepSession->GetLastError();
+
+ CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
+ mHandle.c_str(), errorString.c_str());
+
+ mPCObserver->OnCreateOfferError(
+ *buildJSErrorData(result, errorString), rv);
+ } else {
+ mPCObserver->OnCreateOfferSuccess(ObString(offer.c_str()), rv);
+ }
+ }));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CreateAnswer() {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ CSFLogDebug(LOGTAG, "CreateAnswer()");
+
+ STAMP_TIMECARD(mTimeCard, "Create Answer");
+ // TODO(bug 1098015): Once RTCAnswerOptions is standardized, we'll need to
+ // add it as a param to CreateAnswer, and convert it here.
+ JsepAnswerOptions options;
+
+ mThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<PeerConnectionImpl>(this), options] {
+ std::string answer;
+
+ JsepSession::Result result =
+ mJsepSession->CreateAnswer(options, &answer);
+ JSErrorResult rv;
+ if (result.mError.isSome()) {
+ std::string errorString = mJsepSession->GetLastError();
+
+ CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
+ mHandle.c_str(), errorString.c_str());
+
+ mPCObserver->OnCreateAnswerError(
+ *buildJSErrorData(result, errorString), rv);
+ } else {
+ mPCObserver->OnCreateAnswerSuccess(ObString(answer.c_str()), rv);
+ }
+ }));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (!aSDP) {
+ CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ JSErrorResult rv;
+ STAMP_TIMECARD(mTimeCard, "Set Local Description");
+
+ if (mMedia->AnyLocalTrackHasPeerIdentity()) {
+ mPrivacyRequested = Some(true);
+ }
+
+ mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry;
+ sdpEntry.mIsLocal = true;
+ sdpEntry.mTimestamp = mTimestampMaker.GetNow();
+ sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP);
+ auto appendHistory = [&]() {
+ if (!mSdpHistory.AppendElement(sdpEntry, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ };
+
+ mLocalRequestedSDP = aSDP;
+
+ bool wasRestartingIce = mJsepSession->IsIceRestarting();
+ JsepSdpType sdpType;
+ switch (aAction) {
+ case IPeerConnection::kActionOffer:
+ sdpType = mozilla::kJsepSdpOffer;
+ break;
+ case IPeerConnection::kActionAnswer:
+ sdpType = mozilla::kJsepSdpAnswer;
+ break;
+ case IPeerConnection::kActionPRAnswer:
+ sdpType = mozilla::kJsepSdpPranswer;
+ break;
+ case IPeerConnection::kActionRollback:
+ sdpType = mozilla::kJsepSdpRollback;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ appendHistory();
+ return NS_ERROR_FAILURE;
+ }
+ JsepSession::Result result =
+ mJsepSession->SetLocalDescription(sdpType, mLocalRequestedSDP);
+ if (result.mError.isSome()) {
+ std::string errorString = mJsepSession->GetLastError();
+ CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
+ mHandle.c_str(), errorString.c_str());
+ mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString),
+ rv);
+ sdpEntry.mErrors = GetLastSdpParsingErrors();
+ } else {
+ if (wasRestartingIce) {
+ RecordIceRestartStatistics(sdpType);
+ }
+
+ OnSetDescriptionSuccess(sdpType, false);
+ }
+
+ appendHistory();
+ return NS_OK;
+}
+
+static void DeferredSetRemote(const std::string& aPcHandle, int32_t aAction,
+ const std::string& aSdp) {
+ PeerConnectionWrapper wrapper(aPcHandle);
+
+ if (wrapper.impl()) {
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ MOZ_CRASH(
+ "Why is DeferredSetRemote being executed when the "
+ "PeerConnectionCtx isn't ready?");
+ }
+ wrapper.impl()->SetRemoteDescription(aAction, aSdp.c_str());
+ }
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (!aSDP) {
+ CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ JSErrorResult jrv;
+ if (action == IPeerConnection::kActionOffer) {
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ // Uh oh. We're not ready yet. Enqueue this operation. (This must be a
+ // remote offer, or else we would not have gotten this far)
+ PeerConnectionCtx::GetInstance()->queueJSEPOperation(WrapRunnableNM(
+ DeferredSetRemote, mHandle, action, std::string(aSDP)));
+ STAMP_TIMECARD(mTimeCard, "Deferring SetRemote (not ready)");
+ return NS_OK;
+ }
+
+ nsresult nrv = ConfigureJsepSessionCodecs();
+ if (NS_FAILED(nrv)) {
+ CSFLogError(LOGTAG, "Failed to configure codecs");
+ return nrv;
+ }
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Set Remote Description");
+
+ mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry;
+ sdpEntry.mIsLocal = false;
+ sdpEntry.mTimestamp = mTimestampMaker.GetNow();
+ sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP);
+ auto appendHistory = [&]() {
+ if (!mSdpHistory.AppendElement(sdpEntry, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ };
+
+ mRemoteRequestedSDP = aSDP;
+ bool wasRestartingIce = mJsepSession->IsIceRestarting();
+ JsepSdpType sdpType;
+ switch (action) {
+ case IPeerConnection::kActionOffer:
+ sdpType = mozilla::kJsepSdpOffer;
+ break;
+ case IPeerConnection::kActionAnswer:
+ sdpType = mozilla::kJsepSdpAnswer;
+ break;
+ case IPeerConnection::kActionPRAnswer:
+ sdpType = mozilla::kJsepSdpPranswer;
+ break;
+ case IPeerConnection::kActionRollback:
+ sdpType = mozilla::kJsepSdpRollback;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ auto originalTransceivers = mJsepSession->GetTransceivers();
+ JsepSession::Result result =
+ mJsepSession->SetRemoteDescription(sdpType, mRemoteRequestedSDP);
+ if (result.mError.isSome()) {
+ std::string errorString = mJsepSession->GetLastError();
+ sdpEntry.mErrors = GetLastSdpParsingErrors();
+ CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
+ mHandle.c_str(), errorString.c_str());
+ mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString),
+ jrv);
+ } else {
+ for (const auto& [id, jsepTransceiver] : mJsepSession->GetTransceivers()) {
+ if (jsepTransceiver->GetMediaType() ==
+ SdpMediaSection::MediaType::kApplication) {
+ continue;
+ }
+
+ if (originalTransceivers.count(id)) {
+ continue;
+ }
+
+ // New audio or video transceiver, need to tell JS about it.
+ RefPtr<TransceiverImpl> transceiverImpl =
+ CreateTransceiverImpl(jsepTransceiver, nullptr, jrv);
+ if (jrv.Failed()) {
+ appendHistory();
+ return NS_ERROR_FAILURE;
+ }
+
+ const JsepTrack& receiving(jsepTransceiver->mRecvTrack);
+ CSFLogInfo(LOGTAG, "%s: pc = %s, asking JS to create transceiver",
+ __FUNCTION__, mHandle.c_str());
+ switch (receiving.GetMediaType()) {
+ case SdpMediaSection::MediaType::kAudio:
+ mPCObserver->OnTransceiverNeeded(NS_ConvertASCIItoUTF16("audio"),
+ *transceiverImpl, jrv);
+ break;
+ case SdpMediaSection::MediaType::kVideo:
+ mPCObserver->OnTransceiverNeeded(NS_ConvertASCIItoUTF16("video"),
+ *transceiverImpl, jrv);
+ break;
+ default:
+ MOZ_RELEASE_ASSERT(false);
+ }
+
+ if (jrv.Failed()) {
+ nsresult rv = jrv.StealNSResult();
+ CSFLogError(LOGTAG,
+ "%s: pc = %s, OnTransceiverNeeded failed. "
+ "This should never happen. rv = %d",
+ __FUNCTION__, mHandle.c_str(), static_cast<int>(rv));
+ MOZ_CRASH();
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (wasRestartingIce) {
+ RecordIceRestartStatistics(sdpType);
+ }
+
+ OnSetDescriptionSuccess(sdpType, true);
+ }
+
+ appendHistory();
+ return NS_OK;
+}
+
+already_AddRefed<dom::Promise> PeerConnectionImpl::GetStats(
+ MediaStreamTrack* aSelector) {
+ if (NS_FAILED(CheckApiState(false))) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(global, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.StealNSResult();
+ return nullptr;
+ }
+
+ GetStats(aSelector, false)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [promise,
+ window = mWindow](UniquePtr<dom::RTCStatsReportInternal>&& aReport) {
+ RefPtr<RTCStatsReport> report(new RTCStatsReport(window));
+ report->Incorporate(*aReport);
+ promise->MaybeResolve(std::move(report));
+ },
+ [promise, window = mWindow](nsresult aError) {
+ RefPtr<RTCStatsReport> report(new RTCStatsReport(window));
+ promise->MaybeResolve(std::move(report));
+ });
+
+ return promise.forget();
+}
+
+void PeerConnectionImpl::GetRemoteStreams(
+ nsTArray<RefPtr<DOMMediaStream>>& aStreamsOut) const {
+ aStreamsOut = mReceiveStreams.Clone();
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::AddIceCandidate(
+ const char* aCandidate, const char* aMid, const char* aUfrag,
+ const dom::Nullable<unsigned short>& aLevel) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (mForceIceTcp &&
+ std::string::npos != std::string(aCandidate).find(" UDP ")) {
+ CSFLogError(LOGTAG, "Blocking remote UDP candidate: %s", aCandidate);
+ return NS_OK;
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Add Ice Candidate");
+
+ CSFLogDebug(LOGTAG, "AddIceCandidate: %s %s", aCandidate, aUfrag);
+
+ std::string transportId;
+ Maybe<unsigned short> level;
+ if (!aLevel.IsNull()) {
+ level = Some(aLevel.Value());
+ }
+ JsepSession::Result result = mJsepSession->AddRemoteIceCandidate(
+ aCandidate, aMid, level, aUfrag, &transportId);
+
+ if (!result.mError.isSome()) {
+ // We do not bother PCMedia about this before offer/answer concludes.
+ // Once offer/answer concludes, PCMedia will extract these candidates from
+ // the remote SDP.
+ if (mSignalingState == RTCSignalingState::Stable && !transportId.empty()) {
+ mMedia->AddIceCandidate(aCandidate, transportId, aUfrag);
+ mRawTrickledCandidates.push_back(aCandidate);
+ }
+ // Spec says we queue a task for these updates
+ mThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<PeerConnectionImpl>(this)] {
+ if (IsClosed()) {
+ return;
+ }
+ mPendingRemoteDescription =
+ mJsepSession->GetRemoteDescription(kJsepDescriptionPending);
+ mCurrentRemoteDescription =
+ mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent);
+ JSErrorResult rv;
+ mPCObserver->OnAddIceCandidateSuccess(rv);
+ }));
+ } else {
+ std::string errorString = mJsepSession->GetLastError();
+
+ CSFLogError(LOGTAG,
+ "Failed to incorporate remote candidate into SDP:"
+ " res = %u, candidate = %s, level = %i, error = %s",
+ static_cast<unsigned>(*result.mError), aCandidate,
+ level.valueOr(-1), errorString.c_str());
+
+ mThread->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [this, self = RefPtr<PeerConnectionImpl>(this), errorString, result] {
+ if (IsClosed()) {
+ return;
+ }
+ JSErrorResult rv;
+ mPCObserver->OnAddIceCandidateError(
+ *buildJSErrorData(result, errorString), rv);
+ }));
+ }
+
+ return NS_OK;
+}
+
+void PeerConnectionImpl::UpdateNetworkState(bool online) {
+ if (!mMedia) {
+ return;
+ }
+ mMedia->UpdateNetworkState(online);
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CloseStreams() {
+ PC_AUTO_ENTER_API_CALL(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+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);
+ Document* doc = GetWindow()->GetExtantDoc();
+ if (!doc) {
+ CSFLogInfo(LOGTAG, "Can't update principal on streams; document gone");
+ return NS_ERROR_FAILURE;
+ }
+ MediaStreamTrack* allTracks = nullptr;
+ mMedia->UpdateSinkIdentity_m(allTracks, doc->NodePrincipal(),
+ mPeerIdentity);
+ }
+ return NS_OK;
+}
+
+nsresult PeerConnectionImpl::OnAlpnNegotiated(bool aPrivacyRequested) {
+ PC_AUTO_ENTER_API_CALL(false);
+ if (mPrivacyRequested.isSome()) {
+ MOZ_DIAGNOSTIC_ASSERT(*mPrivacyRequested == aPrivacyRequested);
+ return NS_OK;
+ }
+
+ mPrivacyRequested = Some(aPrivacyRequested);
+ return NS_OK;
+}
+
+void PeerConnectionImpl::PrincipalChanged(MediaStreamTrack* aTrack) {
+ Document* doc = GetWindow()->GetExtantDoc();
+ if (doc) {
+ mMedia->UpdateSinkIdentity_m(aTrack, doc->NodePrincipal(), mPeerIdentity);
+ } else {
+ CSFLogInfo(LOGTAG, "Can't update sink principal; document gone");
+ }
+}
+
+void PeerConnectionImpl::OnMediaError(const std::string& aError) {
+ CSFLogError(LOGTAG, "Encountered media error! %s", aError.c_str());
+ // TODO: Let content know about this somehow.
+}
+
+bool PeerConnectionImpl::ShouldDumpPacket(size_t level,
+ dom::mozPacketDumpType type,
+ bool sending) const {
+ if (!mPacketDumpEnabled) {
+ return false;
+ }
+
+ MutexAutoLock lock(mPacketDumpFlagsMutex);
+
+ const std::vector<unsigned>* packetDumpFlags;
+
+ if (sending) {
+ packetDumpFlags = &mSendPacketDumpFlags;
+ } else {
+ packetDumpFlags = &mRecvPacketDumpFlags;
+ }
+
+ if (level < packetDumpFlags->size()) {
+ unsigned flag = 1 << (unsigned)type;
+ return flag & packetDumpFlags->at(level);
+ }
+
+ return false;
+}
+
+void PeerConnectionImpl::DumpPacket_m(size_t level, dom::mozPacketDumpType type,
+ bool sending,
+ UniquePtr<uint8_t[]>& packet,
+ size_t size) {
+ if (IsClosed()) {
+ return;
+ }
+
+ if (!ShouldDumpPacket(level, type, sending)) {
+ return;
+ }
+
+ // TODO: Is this efficient? Should we try grabbing our JS ctx from somewhere
+ // else?
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(GetWindow())) {
+ return;
+ }
+
+ JS::Rooted<JSObject*> jsobj(
+ jsapi.cx(),
+ JS::NewArrayBufferWithContents(jsapi.cx(), size, packet.release()));
+
+ RootedSpiderMonkeyInterface<ArrayBuffer> arrayBuffer(jsapi.cx());
+ if (!arrayBuffer.Init(jsobj)) {
+ return;
+ }
+
+ JSErrorResult jrv;
+ mPCObserver->OnPacket(level, type, sending, arrayBuffer, jrv);
+}
+
+bool PeerConnectionImpl::HostnameInPref(const char* aPref, nsIURI* aDocURI) {
+ auto HostInDomain = [](const nsCString& aHost, const nsCString& aPattern) {
+ int32_t patternOffset = 0;
+ int32_t hostOffset = 0;
+
+ // Act on '*.' wildcard in the left-most position in a domain pattern.
+ if (StringBeginsWith(aPattern, nsCString("*."))) {
+ patternOffset = 2;
+
+ // Ignore the lowest level sub-domain for the hostname.
+ hostOffset = aHost.FindChar('.') + 1;
+
+ if (hostOffset <= 1) {
+ // Reject a match between a wildcard and a TLD or '.foo' form.
+ return false;
+ }
+ }
+
+ nsDependentCString hostRoot(aHost, hostOffset);
+ return hostRoot.EqualsIgnoreCase(aPattern.BeginReading() + patternOffset);
+ };
+
+ if (!aDocURI) {
+ return false;
+ }
+
+ nsCString hostName;
+ aDocURI->GetAsciiHost(hostName); // normalize UTF8 to ASCII equivalent
+ nsCString domainList;
+ nsresult nr = Preferences::GetCString(aPref, domainList);
+
+ if (NS_FAILED(nr)) {
+ return false;
+ }
+
+ domainList.StripWhitespace();
+
+ if (domainList.IsEmpty() || hostName.IsEmpty()) {
+ return false;
+ }
+
+ // Get UTF8 to ASCII domain name normalization service
+ nsresult rv;
+ nsCOMPtr<nsIIDNService> idnService =
+ do_GetService("@mozilla.org/network/idn-service;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ // Test each domain name in the comma separated list
+ // after converting from UTF8 to ASCII. Each domain
+ // must match exactly or have a single leading '*.' wildcard.
+ for (const nsACString& each : domainList.Split(',')) {
+ nsCString domainName;
+ rv = idnService->ConvertUTF8toACE(each, domainName);
+ if (NS_SUCCEEDED(rv)) {
+ if (HostInDomain(hostName, domainName)) {
+ return true;
+ }
+ } else {
+ NS_WARNING("Failed to convert UTF-8 host to ASCII");
+ }
+ }
+
+ return false;
+}
+
+nsresult PeerConnectionImpl::EnablePacketDump(unsigned long level,
+ dom::mozPacketDumpType type,
+ bool sending) {
+ mPacketDumpEnabled = true;
+ std::vector<unsigned>* packetDumpFlags;
+ if (sending) {
+ packetDumpFlags = &mSendPacketDumpFlags;
+ } else {
+ packetDumpFlags = &mRecvPacketDumpFlags;
+ }
+
+ unsigned flag = 1 << (unsigned)type;
+
+ MutexAutoLock lock(mPacketDumpFlagsMutex);
+ if (level >= packetDumpFlags->size()) {
+ packetDumpFlags->resize(level + 1);
+ }
+
+ (*packetDumpFlags)[level] |= flag;
+ return NS_OK;
+}
+
+nsresult PeerConnectionImpl::DisablePacketDump(unsigned long level,
+ dom::mozPacketDumpType type,
+ bool sending) {
+ std::vector<unsigned>* packetDumpFlags;
+ if (sending) {
+ packetDumpFlags = &mSendPacketDumpFlags;
+ } else {
+ packetDumpFlags = &mRecvPacketDumpFlags;
+ }
+
+ unsigned flag = 1 << (unsigned)type;
+
+ MutexAutoLock lock(mPacketDumpFlagsMutex);
+ if (level < packetDumpFlags->size()) {
+ (*packetDumpFlags)[level] &= ~flag;
+ }
+
+ return NS_OK;
+}
+
+void PeerConnectionImpl::StampTimecard(const char* aEvent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ STAMP_TIMECARD(mTimeCard, aEvent);
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::ReplaceTrackNoRenegotiation(TransceiverImpl& aTransceiver,
+ MediaStreamTrack* aWithTrack) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ RefPtr<dom::MediaStreamTrack> oldSendTrack(aTransceiver.GetSendTrack());
+ if (oldSendTrack) {
+ oldSendTrack->RemovePrincipalChangeObserver(this);
+ }
+
+ nsresult rv = aTransceiver.UpdateSendTrack(aWithTrack);
+
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Failed to update transceiver: %d",
+ static_cast<int>(rv));
+ return rv;
+ }
+
+ if (aWithTrack) {
+ aWithTrack->AddPrincipalChangeObserver(this);
+ PrincipalChanged(aWithTrack);
+ }
+
+ if (aTransceiver.IsVideo()) {
+ // We update the media pipelines here so we can apply different codec
+ // settings for different sources (e.g. screensharing as opposed to camera.)
+ MediaSourceEnum oldSource = oldSendTrack
+ ? oldSendTrack->GetSource().GetMediaSource()
+ : MediaSourceEnum::Camera;
+ MediaSourceEnum newSource = aWithTrack
+ ? aWithTrack->GetSource().GetMediaSource()
+ : MediaSourceEnum::Camera;
+ if (oldSource != newSource) {
+ if (NS_WARN_IF(NS_FAILED(rv = aTransceiver.UpdateConduit()))) {
+ CSFLogError(LOGTAG, "Error Updating VideoConduit");
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult PeerConnectionImpl::CalculateFingerprint(
+ const std::string& algorithm, std::vector<uint8_t>* fingerprint) const {
+ DtlsDigest digest(algorithm);
+
+ MOZ_ASSERT(fingerprint);
+ const UniqueCERTCertificate& cert = mCertificate->Certificate();
+ nsresult rv = DtlsIdentity::ComputeFingerprint(cert, &digest);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Unable to calculate certificate fingerprint, rv=%u",
+ static_cast<unsigned>(rv));
+ return rv;
+ }
+ *fingerprint = digest.value_;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::GetFingerprint(char** fingerprint) {
+ MOZ_ASSERT(fingerprint);
+ MOZ_ASSERT(mCertificate);
+ std::vector<uint8_t> fp;
+ nsresult rv = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ std::ostringstream os;
+ os << DtlsIdentity::DEFAULT_HASH_ALGORITHM << ' '
+ << SdpFingerprintAttributeList::FormatFingerprint(fp);
+ std::string fpStr = os.str();
+
+ char* tmp = new char[fpStr.size() + 1];
+ std::copy(fpStr.begin(), fpStr.end(), tmp);
+ tmp[fpStr.size()] = '\0';
+
+ *fingerprint = tmp;
+ return NS_OK;
+}
+
+void PeerConnectionImpl::GetCurrentLocalDescription(nsAString& aSDP) const {
+ aSDP = NS_ConvertASCIItoUTF16(mCurrentLocalDescription.c_str());
+}
+
+void PeerConnectionImpl::GetPendingLocalDescription(nsAString& aSDP) const {
+ aSDP = NS_ConvertASCIItoUTF16(mPendingLocalDescription.c_str());
+}
+
+void PeerConnectionImpl::GetCurrentRemoteDescription(nsAString& aSDP) const {
+ aSDP = NS_ConvertASCIItoUTF16(mCurrentRemoteDescription.c_str());
+}
+
+void PeerConnectionImpl::GetPendingRemoteDescription(nsAString& aSDP) const {
+ aSDP = NS_ConvertASCIItoUTF16(mPendingRemoteDescription.c_str());
+}
+
+dom::Nullable<bool> PeerConnectionImpl::GetCurrentOfferer() const {
+ dom::Nullable<bool> result;
+ if (mCurrentOfferer.isSome()) {
+ result.SetValue(*mCurrentOfferer);
+ }
+ return result;
+}
+
+dom::Nullable<bool> PeerConnectionImpl::GetPendingOfferer() const {
+ dom::Nullable<bool> result;
+ if (mPendingOfferer.isSome()) {
+ result.SetValue(*mPendingOfferer);
+ }
+ return result;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::SignalingState(RTCSignalingState* aState) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aState);
+
+ *aState = mSignalingState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::IceConnectionState(RTCIceConnectionState* aState) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aState);
+
+ *aState = mIceConnectionState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::IceGatheringState(RTCIceGatheringState* aState) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aState);
+
+ *aState = mIceGatheringState;
+ return NS_OK;
+}
+
+nsresult PeerConnectionImpl::CheckApiState(bool assert_ice_ready) const {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(mTrickle || !assert_ice_ready ||
+ (mIceGatheringState == RTCIceGatheringState::Complete));
+
+ if (IsClosed()) {
+ CSFLogError(LOGTAG, "%s: called API while closed", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ if (!mMedia) {
+ CSFLogError(LOGTAG, "%s: called API with disposed mMedia", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::Close() {
+ CSFLogDebug(LOGTAG, "%s: for %s", __FUNCTION__, mHandle.c_str());
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ CloseInt();
+ // Uncount this connection as active on the inner window upon close.
+ if (mWindow && mActiveOnWindow) {
+ mWindow->RemovePeerConnection();
+ mActiveOnWindow = false;
+ }
+
+ return NS_OK;
+}
+
+bool PeerConnectionImpl::PluginCrash(uint32_t aPluginID,
+ const nsAString& aPluginName) {
+ // fire an event to the DOM window if this is "ours"
+ bool result = mMedia ? mMedia->AnyCodecHasPluginID(aPluginID) : false;
+ if (!result) {
+ return false;
+ }
+
+ CSFLogError(LOGTAG, "%s: Our plugin %llu crashed", __FUNCTION__,
+ static_cast<unsigned long long>(aPluginID));
+
+ RefPtr<Document> doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ NS_WARNING("Couldn't get document for PluginCrashed event!");
+ return true;
+ }
+
+ PluginCrashedEventInit init;
+ init.mPluginID = aPluginID;
+ init.mPluginName = aPluginName;
+ init.mSubmittedCrashReport = false;
+ init.mGmpPlugin = true;
+ init.mBubbles = true;
+ init.mCancelable = true;
+
+ RefPtr<PluginCrashedEvent> event =
+ PluginCrashedEvent::Constructor(doc, u"PluginCrashed"_ns, init);
+
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ EventDispatcher::DispatchDOMEvent(mWindow, nullptr, event, nullptr, nullptr);
+
+ return true;
+}
+
+void PeerConnectionImpl::RecordEndOfCallTelemetry() {
+ if (!mCallTelemStarted) {
+ return;
+ }
+ MOZ_RELEASE_ASSERT(!mCallTelemEnded, "Don't end telemetry twice");
+ MOZ_RELEASE_ASSERT(mJsepSession,
+ "Call telemetry only starts after jsep session start");
+ MOZ_RELEASE_ASSERT(mJsepSession->GetNegotiations() > 0,
+ "Call telemetry only starts after first connection");
+
+ // Bitmask used for WEBRTC/LOOP_CALL_TYPE telemetry reporting
+ static const uint32_t kAudioTypeMask = 1;
+ static const uint32_t kVideoTypeMask = 2;
+ static const uint32_t kDataChannelTypeMask = 4;
+
+ // Report end-of-call Telemetry
+ Telemetry::Accumulate(Telemetry::WEBRTC_RENEGOTIATIONS,
+ mJsepSession->GetNegotiations() - 1);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_SEND_TRACK,
+ mMaxSending[SdpMediaSection::MediaType::kVideo]);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_RECEIVE_TRACK,
+ mMaxReceiving[SdpMediaSection::MediaType::kVideo]);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_SEND_TRACK,
+ mMaxSending[SdpMediaSection::MediaType::kAudio]);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_RECEIVE_TRACK,
+ mMaxReceiving[SdpMediaSection::MediaType::kAudio]);
+ // DataChannels appear in both Sending and Receiving
+ Telemetry::Accumulate(Telemetry::WEBRTC_DATACHANNEL_NEGOTIATED,
+ mMaxSending[SdpMediaSection::MediaType::kApplication]);
+ // Enumerated/bitmask: 1 = Audio, 2 = Video, 4 = DataChannel
+ // A/V = 3, A/V/D = 7, etc
+ uint32_t type = 0;
+ if (mMaxSending[SdpMediaSection::MediaType::kAudio] ||
+ mMaxReceiving[SdpMediaSection::MediaType::kAudio]) {
+ type = kAudioTypeMask;
+ }
+ if (mMaxSending[SdpMediaSection::MediaType::kVideo] ||
+ mMaxReceiving[SdpMediaSection::MediaType::kVideo]) {
+ type |= kVideoTypeMask;
+ }
+ if (mMaxSending[SdpMediaSection::MediaType::kApplication]) {
+ type |= kDataChannelTypeMask;
+ }
+ Telemetry::Accumulate(Telemetry::WEBRTC_CALL_TYPE, type);
+
+ MOZ_RELEASE_ASSERT(mWindow);
+ auto found = sCallDurationTimers.find(mWindow->WindowID());
+ if (found != sCallDurationTimers.end()) {
+ found->second.UnregisterConnection();
+ if (found->second.IsStopped()) {
+ sCallDurationTimers.erase(found);
+ }
+ }
+ mCallTelemEnded = true;
+}
+
+nsresult PeerConnectionImpl::CloseInt() {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ // We do this at the end of the call because we want to make sure we've waited
+ // for all trickle ICE candidates to come in; this can happen well after we've
+ // transitioned to connected. As a bonus, this allows us to detect race
+ // conditions where a stats dispatch happens right as the PC closes.
+ if (!mPrivateWindow) {
+ RecordLongtermICEStatistics();
+ }
+ RecordEndOfCallTelemetry();
+ CSFLogInfo(LOGTAG,
+ "%s: Closing PeerConnectionImpl %s; "
+ "ending call",
+ __FUNCTION__, mHandle.c_str());
+ if (mJsepSession) {
+ mJsepSession->Close();
+ }
+ if (mDataConnection) {
+ CSFLogInfo(LOGTAG, "%s: Destroying DataChannelConnection %p for %s",
+ __FUNCTION__, (void*)mDataConnection.get(), mHandle.c_str());
+ mDataConnection->Destroy();
+ mDataConnection =
+ nullptr; // it may not go away until the runnables are dead
+ }
+ ShutdownMedia();
+
+ // DataConnection will need to stay alive until all threads/runnables exit
+
+ return NS_OK;
+}
+
+void PeerConnectionImpl::ShutdownMedia() {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ if (!mMedia) return;
+
+ // before we destroy references to local tracks, detach from them
+ for (RefPtr<TransceiverImpl>& transceiver : mMedia->GetTransceivers()) {
+ RefPtr<dom::MediaStreamTrack> track = transceiver->GetSendTrack();
+ if (track) {
+ track->RemovePrincipalChangeObserver(this);
+ }
+ }
+
+ // Forget the reference so that we can transfer it to
+ // SelfDestruct().
+ mMedia.forget().take()->SelfDestruct();
+}
+
+DOMMediaStream* PeerConnectionImpl::GetReceiveStream(
+ const std::string& aId) const {
+ nsString wanted = NS_ConvertASCIItoUTF16(aId.c_str());
+ for (auto& stream : mReceiveStreams) {
+ nsString id;
+ stream->GetId(id);
+ if (id == wanted) {
+ return stream;
+ }
+ }
+ return nullptr;
+}
+
+DOMMediaStream* PeerConnectionImpl::CreateReceiveStream(
+ const std::string& aId) {
+ mReceiveStreams.AppendElement(new DOMMediaStream(mWindow));
+ mReceiveStreams.LastElement()->AssignId(NS_ConvertASCIItoUTF16(aId.c_str()));
+ return mReceiveStreams.LastElement();
+}
+
+void PeerConnectionImpl::OnSetDescriptionSuccess(JsepSdpType sdpType,
+ bool remote) {
+ // Spec says we queue a task for all the stuff that ends up back in JS
+ auto newSignalingState = GetSignalingState();
+
+ mThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<PeerConnectionImpl>(this),
+ newSignalingState, sdpType, remote] {
+ if (IsClosed()) {
+ return;
+ }
+
+ if (HasMedia()) {
+ // Section 4.4.1.5 Set the RTCSessionDescription:
+ if (sdpType == mozilla::kJsepSdpRollback) {
+ // - step 4.5.10, type is rollback
+ mMedia->RollbackRTCDtlsTransports();
+ } else if (!(remote && sdpType == mozilla::kJsepSdpOffer)) {
+ // - step 4.5.9 type is not rollback
+ // - step 4.5.9.1 when remote is false
+ // - step 4.5.9.2.13 when remote is true, type answer or pranswer
+ // More simply: not rollback, and not for remote offers.
+ bool markAsStable = sdpType == kJsepSdpOffer &&
+ mSignalingState == RTCSignalingState::Stable;
+ mMedia->UpdateRTCDtlsTransports(markAsStable);
+ }
+ }
+
+ JSErrorResult jrv;
+ mPCObserver->SyncTransceivers(jrv);
+ if (NS_WARN_IF(jrv.Failed())) {
+ return;
+ }
+ mPendingRemoteDescription =
+ mJsepSession->GetRemoteDescription(kJsepDescriptionPending);
+ mCurrentRemoteDescription =
+ mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent);
+ mPendingLocalDescription =
+ mJsepSession->GetLocalDescription(kJsepDescriptionPending);
+ mCurrentLocalDescription =
+ mJsepSession->GetLocalDescription(kJsepDescriptionCurrent);
+ mPendingOfferer = mJsepSession->IsPendingOfferer();
+ mCurrentOfferer = mJsepSession->IsCurrentOfferer();
+ if (newSignalingState != mSignalingState) {
+ mSignalingState = newSignalingState;
+ mPCObserver->OnStateChange(PCObserverStateType::SignalingState, jrv);
+ }
+
+ // TODO: Spec says that we should do this even if JS closes the PC
+ // during the signalingstatechange event. We'd need to refactor a
+ // little here to make this possible.
+ if (remote && HasMedia()) {
+ dom::RTCRtpReceiver::StreamAssociationChanges changes;
+ for (const auto& transceiver : mMedia->GetTransceivers()) {
+ transceiver->Receiver()->UpdateStreams(&changes);
+ }
+
+ for (const auto& track : changes.mTracksToMute) {
+ // This sets the muted state for track and all its clones.
+ static_cast<RemoteTrackSource&>(track->GetSource()).SetMuted(true);
+ }
+
+ for (const auto& association : changes.mStreamAssociationsRemoved) {
+ RefPtr<DOMMediaStream> stream =
+ GetReceiveStream(association.mStreamId);
+ if (stream && stream->HasTrack(*association.mTrack)) {
+ stream->RemoveTrackInternal(association.mTrack);
+ }
+ }
+
+ // TODO(Bug 1241291): For legacy event, remove eventually
+ std::vector<RefPtr<DOMMediaStream>> newStreams;
+
+ for (const auto& association : changes.mStreamAssociationsAdded) {
+ RefPtr<DOMMediaStream> stream =
+ GetReceiveStream(association.mStreamId);
+ if (!stream) {
+ stream = CreateReceiveStream(association.mStreamId);
+ newStreams.push_back(stream);
+ }
+
+ if (!stream->HasTrack(*association.mTrack)) {
+ stream->AddTrackInternal(association.mTrack);
+ }
+ }
+
+ for (const auto& trackEvent : changes.mTrackEvents) {
+ dom::Sequence<OwningNonNull<DOMMediaStream>> streams;
+ for (const auto& id : trackEvent.mStreamIds) {
+ RefPtr<DOMMediaStream> stream = GetReceiveStream(id);
+ if (!stream) {
+ MOZ_ASSERT(false);
+ continue;
+ }
+ if (!streams.AppendElement(*stream, fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which
+ // might involve multiple reallocations) and potentially
+ // crashing here, SetCapacity could be called outside the loop
+ // once.
+ mozalloc_handle_oom(0);
+ }
+ }
+ mPCObserver->FireTrackEvent(*trackEvent.mReceiver, streams, jrv);
+ }
+
+ // TODO(Bug 1241291): Legacy event, remove eventually
+ for (const auto& stream : newStreams) {
+ mPCObserver->FireStreamEvent(*stream, jrv);
+ }
+ }
+
+ mPCObserver->OnSetDescriptionSuccess(jrv);
+ }));
+
+ // We do this after queueing the above task, to ensure that ICE state
+ // changes don't start happening before sRD finishes.
+
+ // Did we just apply a local description?
+ if (!remote) {
+ // We'd like to handle this in PeerConnectionMedia::UpdateNetworkState.
+ // Unfortunately, if the WiFi switch happens quickly, we never see
+ // that state change. We need to detect the ice restart here and
+ // reset the PeerConnectionMedia's stun addresses so they are
+ // regathered when PeerConnectionMedia::GatherIfReady is called.
+ if (mJsepSession->IsIceRestarting()) {
+ mMedia->ResetStunAddrsForIceRestart();
+ }
+ mMedia->EnsureTransports(*mJsepSession);
+ }
+
+ if (mJsepSession->GetState() != kJsepStateStable) {
+ return; // The rest of this stuff is done only when offer/answer is done
+ }
+
+ // If we're rolling back a local offer, we might need to remove some
+ // transports, and stomp some MediaPipeline setup, but nothing further
+ // needs to be done.
+ mMedia->UpdateTransports(*mJsepSession, mForceIceTcp);
+ if (NS_FAILED(mMedia->UpdateMediaPipelines())) {
+ CSFLogError(LOGTAG, "Error Updating MediaPipelines");
+ NS_ASSERTION(false,
+ "Error Updating MediaPipelines in OnSetDescriptionSuccess()");
+ // XXX what now? Not much we can do but keep going, without major
+ // restructuring
+ }
+
+ if (sdpType != kJsepSdpRollback) {
+ InitializeDataChannel();
+ mMedia->StartIceChecks(*mJsepSession);
+ }
+
+ // Telemetry: record info on the current state of streams/renegotiations/etc
+ // Note: this code gets run on rollbacks as well!
+
+ // Update the max channels used with each direction for each type
+ uint16_t receiving[SdpMediaSection::kMediaTypes];
+ uint16_t sending[SdpMediaSection::kMediaTypes];
+ mJsepSession->CountTracksAndDatachannels(receiving, sending);
+ for (size_t i = 0; i < SdpMediaSection::kMediaTypes; i++) {
+ if (mMaxReceiving[i] < receiving[i]) {
+ mMaxReceiving[i] = receiving[i];
+ }
+ if (mMaxSending[i] < sending[i]) {
+ mMaxSending[i] = sending[i];
+ }
+ }
+}
+
+RTCSignalingState PeerConnectionImpl::GetSignalingState() const {
+ switch (mJsepSession->GetState()) {
+ case kJsepStateStable:
+ return RTCSignalingState::Stable;
+ break;
+ case kJsepStateHaveLocalOffer:
+ return RTCSignalingState::Have_local_offer;
+ break;
+ case kJsepStateHaveRemoteOffer:
+ return RTCSignalingState::Have_remote_offer;
+ break;
+ case kJsepStateHaveLocalPranswer:
+ return RTCSignalingState::Have_local_pranswer;
+ break;
+ case kJsepStateHaveRemotePranswer:
+ return RTCSignalingState::Have_remote_pranswer;
+ break;
+ case kJsepStateClosed:
+ return RTCSignalingState::Closed;
+ break;
+ }
+ MOZ_CRASH("Invalid JSEP state");
+}
+
+bool PeerConnectionImpl::IsClosed() const {
+ return mSignalingState == RTCSignalingState::Closed;
+}
+
+bool PeerConnectionImpl::HasMedia() const { return mMedia; }
+
+PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle)
+ : impl_(nullptr) {
+ if (!PeerConnectionCtx::isActive() ||
+ (PeerConnectionCtx::GetInstance()->mPeerConnections.find(handle) ==
+ PeerConnectionCtx::GetInstance()->mPeerConnections.end())) {
+ return;
+ }
+
+ PeerConnectionImpl* impl =
+ PeerConnectionCtx::GetInstance()->mPeerConnections[handle];
+
+ if (!impl->media()) return;
+
+ impl_ = impl;
+}
+
+const RefPtr<MediaTransportHandler> PeerConnectionImpl::GetTransportHandler()
+ const {
+ return mTransportHandler;
+}
+
+const std::string& PeerConnectionImpl::GetHandle() {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mHandle;
+}
+
+const std::string& PeerConnectionImpl::GetName() {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mName;
+}
+
+void PeerConnectionImpl::CandidateReady(const std::string& candidate,
+ const std::string& transportId,
+ const std::string& ufrag) {
+ PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
+
+ if (mForceIceTcp && std::string::npos != candidate.find(" UDP ")) {
+ CSFLogWarn(LOGTAG, "Blocking local UDP candidate: %s", candidate.c_str());
+ return;
+ }
+
+ // One of the very few places we still use level; required by the JSEP API
+ uint16_t level = 0;
+ std::string mid;
+ bool skipped = false;
+ nsresult res = mJsepSession->AddLocalIceCandidate(
+ candidate, transportId, ufrag, &level, &mid, &skipped);
+
+ if (NS_FAILED(res)) {
+ std::string errorString = mJsepSession->GetLastError();
+
+ CSFLogError(LOGTAG,
+ "Failed to incorporate local candidate into SDP:"
+ " res = %u, candidate = %s, transport-id = %s,"
+ " error = %s",
+ static_cast<unsigned>(res), candidate.c_str(),
+ transportId.c_str(), errorString.c_str());
+ return;
+ }
+
+ if (skipped) {
+ CSFLogDebug(LOGTAG,
+ "Skipped adding local candidate %s (transport-id %s) "
+ "to SDP, this typically happens because the m-section "
+ "is bundled, which means it doesn't make sense for it "
+ "to have its own transport-related attributes.",
+ candidate.c_str(), transportId.c_str());
+ return;
+ }
+
+ mPendingLocalDescription =
+ mJsepSession->GetLocalDescription(kJsepDescriptionPending);
+ mCurrentLocalDescription =
+ mJsepSession->GetLocalDescription(kJsepDescriptionCurrent);
+ CSFLogDebug(LOGTAG, "Passing local candidate to content: %s",
+ candidate.c_str());
+ SendLocalIceCandidateToContent(level, mid, candidate, ufrag);
+}
+
+void PeerConnectionImpl::SendLocalIceCandidateToContent(
+ uint16_t level, const std::string& mid, const std::string& candidate,
+ const std::string& ufrag) {
+ JSErrorResult rv;
+ mPCObserver->OnIceCandidate(level, ObString(mid.c_str()),
+ ObString(candidate.c_str()),
+ ObString(ufrag.c_str()), rv);
+}
+
+void PeerConnectionImpl::IceConnectionStateChange(
+ dom::RTCIceConnectionState domState) {
+ PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
+
+ CSFLogDebug(LOGTAG, "%s: %d", __FUNCTION__, static_cast<int>(domState));
+
+ if (domState == mIceConnectionState) {
+ // no work to be done since the states are the same.
+ // this can happen during ICE rollback situations.
+ return;
+ }
+
+ mIceConnectionState = domState;
+
+ // Uncount this connection as active on the inner window upon close.
+ if (mWindow && mActiveOnWindow &&
+ mIceConnectionState == RTCIceConnectionState::Closed) {
+ mWindow->RemovePeerConnection();
+ mActiveOnWindow = false;
+ }
+
+ // Would be nice if we had a means of converting one of these dom enums
+ // to a string that wasn't almost as much text as this switch statement...
+ switch (mIceConnectionState) {
+ case RTCIceConnectionState::New:
+ STAMP_TIMECARD(mTimeCard, "Ice state: new");
+ break;
+ case RTCIceConnectionState::Checking:
+ // For telemetry
+ mIceStartTime = TimeStamp::Now();
+ STAMP_TIMECARD(mTimeCard, "Ice state: checking");
+ break;
+ case RTCIceConnectionState::Connected:
+ STAMP_TIMECARD(mTimeCard, "Ice state: connected");
+ StartCallTelem();
+ break;
+ case RTCIceConnectionState::Completed:
+ STAMP_TIMECARD(mTimeCard, "Ice state: completed");
+ break;
+ case RTCIceConnectionState::Failed:
+ STAMP_TIMECARD(mTimeCard, "Ice state: failed");
+ break;
+ case RTCIceConnectionState::Disconnected:
+ STAMP_TIMECARD(mTimeCard, "Ice state: disconnected");
+ break;
+ case RTCIceConnectionState::Closed:
+ STAMP_TIMECARD(mTimeCard, "Ice state: closed");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected mIceConnectionState!");
+ }
+
+ WrappableJSErrorResult rv;
+ mPCObserver->OnStateChange(PCObserverStateType::IceConnectionState, rv);
+}
+
+void PeerConnectionImpl::OnCandidateFound(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo) {
+ if (!aCandidateInfo.mDefaultHostRtp.empty()) {
+ UpdateDefaultCandidate(aCandidateInfo.mDefaultHostRtp,
+ aCandidateInfo.mDefaultPortRtp,
+ aCandidateInfo.mDefaultHostRtcp,
+ aCandidateInfo.mDefaultPortRtcp, aTransportId);
+ }
+ CandidateReady(aCandidateInfo.mCandidate, aTransportId,
+ aCandidateInfo.mUfrag);
+}
+
+void PeerConnectionImpl::IceGatheringStateChange(
+ dom::RTCIceGatheringState state) {
+ PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
+
+ CSFLogDebug(LOGTAG, "%s %d", __FUNCTION__, static_cast<int>(state));
+ if (mIceGatheringState == state) {
+ return;
+ }
+
+ mIceGatheringState = state;
+
+ // Would be nice if we had a means of converting one of these dom enums
+ // to a string that wasn't almost as much text as this switch statement...
+ switch (mIceGatheringState) {
+ case RTCIceGatheringState::New:
+ STAMP_TIMECARD(mTimeCard, "Ice gathering state: new");
+ break;
+ case RTCIceGatheringState::Gathering:
+ STAMP_TIMECARD(mTimeCard, "Ice gathering state: gathering");
+ break;
+ case RTCIceGatheringState::Complete:
+ STAMP_TIMECARD(mTimeCard, "Ice gathering state: complete");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected mIceGatheringState!");
+ }
+
+ JSErrorResult rv;
+ mPCObserver->OnStateChange(PCObserverStateType::IceGatheringState, rv);
+}
+
+void PeerConnectionImpl::UpdateDefaultCandidate(
+ const std::string& defaultAddr, uint16_t defaultPort,
+ const std::string& defaultRtcpAddr, uint16_t defaultRtcpPort,
+ const std::string& transportId) {
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+ mJsepSession->UpdateDefaultCandidate(
+ defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort, transportId);
+}
+
+// TODO(bug 1616937): Move this to RTCRtpSender.
+static UniquePtr<dom::RTCStatsCollection> GetSenderStats_s(
+ const RefPtr<MediaPipelineTransmit>& aPipeline,
+ const nsAutoString& aTrackName) {
+ UniquePtr<dom::RTCStatsCollection> report(new dom::RTCStatsCollection);
+
+ // Add bandwidth estimation stats
+ aPipeline->Conduit()->GetBandwidthEstimation().apply([&](auto& bw) {
+ bw.mTrackIdentifier = aTrackName;
+ if (!report->mBandwidthEstimations.AppendElement(bw, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+
+ auto asVideo = aPipeline->Conduit()->AsVideoSessionConduit();
+
+ nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns;
+ nsString idstr = kind + u"_"_ns;
+ idstr.AppendInt(static_cast<uint32_t>(aPipeline->Level()));
+
+ // TODO(@@NG):ssrcs handle Conduits having multiple stats at the same level
+ // This is pending spec work
+ // Gather pipeline stats.
+ nsString localId = u"outbound_rtp_"_ns + idstr;
+ nsString remoteId;
+ Maybe<uint32_t> ssrc;
+ std::vector<unsigned int> ssrcvals = aPipeline->Conduit()->GetLocalSSRCs();
+ if (!ssrcvals.empty()) {
+ ssrc = Some(ssrcvals[0]);
+ }
+ {
+ // First, fill in remote stat with rtcp receiver data, if present.
+ // ReceiverReports have less information than SenderReports,
+ // so fill in what we can.
+ uint32_t jitterMs;
+ uint32_t packetsReceived;
+ uint64_t bytesReceived;
+ uint32_t packetsLost;
+ Maybe<double> rtt;
+ Maybe<DOMHighResTimeStamp> timestamp =
+ aPipeline->Conduit()->LastRtcpReceived();
+ if (timestamp.isSome() &&
+ aPipeline->Conduit()->GetRTCPReceiverReport(
+ &jitterMs, &packetsReceived, &bytesReceived, &packetsLost, &rtt)) {
+ remoteId = u"outbound_rtcp_"_ns + idstr;
+ RTCRemoteInboundRtpStreamStats s;
+ s.mTimestamp.Construct(*timestamp);
+ s.mId.Construct(remoteId);
+ s.mType.Construct(RTCStatsType::Remote_inbound_rtp);
+ ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); });
+ s.mMediaType.Construct(kind); // mediaType is the old name for kind.
+ s.mKind.Construct(kind);
+ s.mJitter.Construct(double(jitterMs) / 1000);
+ s.mLocalId.Construct(localId);
+ s.mPacketsReceived.Construct(packetsReceived);
+ s.mBytesReceived.Construct(bytesReceived);
+ s.mPacketsLost.Construct(packetsLost);
+ rtt.apply([&s](auto r) { s.mRoundTripTime.Construct(r); });
+ if (!report->mRemoteInboundRtpStreamStats.AppendElement(s, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ // Then, fill in local side (with cross-link to remote only if present)
+ RTCOutboundRtpStreamStats s;
+ s.mTimestamp.Construct(aPipeline->GetNow());
+ s.mId.Construct(localId);
+ s.mType.Construct(RTCStatsType::Outbound_rtp);
+ ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); });
+ s.mMediaType.Construct(kind); // mediaType is the old name for kind.
+ s.mKind.Construct(kind);
+ if (remoteId.Length()) {
+ s.mRemoteId.Construct(remoteId);
+ }
+ s.mPacketsSent.Construct(aPipeline->RtpPacketsSent());
+ s.mBytesSent.Construct(aPipeline->RtpBytesSent());
+
+ // Fill in packet type statistics
+ webrtc::RtcpPacketTypeCounter counters;
+ if (aPipeline->Conduit()->GetSendPacketTypeStats(&counters)) {
+ s.mNackCount.Construct(counters.nack_packets);
+ // Fill in video only packet type stats
+ if (asVideo) {
+ s.mFirCount.Construct(counters.fir_packets);
+ s.mPliCount.Construct(counters.pli_packets);
+ }
+ }
+
+ // Lastly, fill in video encoder stats, and bandwidth estimation if this is
+ // video
+ asVideo.apply([&s](auto conduit) {
+ double framerateMean;
+ double framerateStdDev;
+ double bitrateMean;
+ double bitrateStdDev;
+ uint32_t droppedFrames;
+ uint32_t framesEncoded;
+ Maybe<uint64_t> qpSum;
+ if (conduit->GetVideoEncoderStats(&framerateMean, &framerateStdDev,
+ &bitrateMean, &bitrateStdDev,
+ &droppedFrames, &framesEncoded, &qpSum)) {
+ s.mFramerateMean.Construct(framerateMean);
+ s.mFramerateStdDev.Construct(framerateStdDev);
+ s.mBitrateMean.Construct(bitrateMean);
+ s.mBitrateStdDev.Construct(bitrateStdDev);
+ s.mDroppedFrames.Construct(droppedFrames);
+ s.mFramesEncoded.Construct(framesEncoded);
+ qpSum.apply([&s](uint64_t aQp) { s.mQpSum.Construct(aQp); });
+ }
+ });
+ if (!report->mOutboundRtpStreamStats.AppendElement(s, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ return report;
+}
+
+RefPtr<dom::RTCStatsPromise> PeerConnectionImpl::GetSenderStats(
+ const RefPtr<MediaPipelineTransmit>& aPipeline) {
+ nsAutoString trackName;
+ auto track = aPipeline->GetTrack();
+ if (track) {
+ track->GetId(trackName);
+ }
+ return InvokeAsync(mSTSThread, __func__, [aPipeline, trackName]() {
+ return dom::RTCStatsPromise::CreateAndResolve(
+ GetSenderStats_s(aPipeline, trackName), __func__);
+ });
+}
+
+static UniquePtr<dom::RTCStatsCollection> GetDataChannelStats_s(
+ const RefPtr<DataChannelConnection>& aDataConnection,
+ const DOMHighResTimeStamp aTimestamp) {
+ UniquePtr<dom::RTCStatsCollection> report(new dom::RTCStatsCollection);
+ if (aDataConnection) {
+ aDataConnection->AppendStatsToReport(report, aTimestamp);
+ }
+ return report;
+}
+
+RefPtr<dom::RTCStatsPromise> PeerConnectionImpl::GetDataChannelStats(
+ const RefPtr<DataChannelConnection>& aDataChannelConnection,
+ const DOMHighResTimeStamp aTimestamp) {
+ // Gather stats from DataChannels
+ return InvokeAsync(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aDataChannelConnection, aTimestamp]() {
+ return dom::RTCStatsPromise::CreateAndResolve(
+ GetDataChannelStats_s(aDataChannelConnection, aTimestamp),
+ __func__);
+ });
+}
+
+void PeerConnectionImpl::RecordConduitTelemetry() {
+ if (!mMedia) {
+ return;
+ }
+
+ nsTArray<RefPtr<VideoSessionConduit>> conduits;
+ for (const auto& transceiver : mMedia->GetTransceivers()) {
+ RefPtr<MediaSessionConduit> conduit = transceiver->GetConduit();
+ if (conduit) {
+ auto asVideo = conduit->AsVideoSessionConduit();
+ if (asVideo) {
+ conduits.AppendElement(asVideo.value());
+ }
+ }
+ }
+
+ mSTSThread->Dispatch(
+ NS_NewRunnableFunction(__func__, [conduits = std::move(conduits)]() {
+ for (const auto& conduit : conduits) {
+ conduit->RecordTelemetry();
+ }
+ }));
+}
+
+template <class T>
+void AssignWithOpaqueIds(dom::Sequence<T>& aSource, dom::Sequence<T>& aDest,
+ RefPtr<RTCStatsIdGenerator>& aGenerator) {
+ for (auto& stat : aSource) {
+ stat.mId.Value() = aGenerator->Id(stat.mId.Value());
+ }
+ if (!aDest.AppendElements(aSource, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+}
+
+template <class T>
+void RewriteRemoteIds(dom::Sequence<T>& aList,
+ RefPtr<RTCStatsIdGenerator>& aGenerator) {
+ for (auto& stat : aList) {
+ if (stat.mRemoteId.WasPassed()) {
+ stat.mRemoteId.Value() = aGenerator->Id(stat.mRemoteId.Value());
+ }
+ }
+}
+
+template <class T>
+void RewriteLocalIds(dom::Sequence<T>& aList,
+ RefPtr<RTCStatsIdGenerator>& aGenerator) {
+ for (auto& stat : aList) {
+ if (stat.mLocalId.WasPassed()) {
+ stat.mLocalId.Value() = aGenerator->Id(stat.mLocalId.Value());
+ }
+ }
+}
+
+RefPtr<dom::RTCStatsReportPromise> PeerConnectionImpl::GetStats(
+ dom::MediaStreamTrack* aSelector, bool aInternalStats) {
+ nsTArray<RefPtr<dom::RTCStatsPromise>> promises;
+ DOMHighResTimeStamp now = mTimestampMaker.GetNow();
+
+ if (mMedia) {
+ nsTArray<RefPtr<MediaPipelineTransmit>> sendPipelines;
+ // Gather up pipelines from mMedia so they may be inspected on STS
+ // TODO(bug 1616937): Use RTCRtpSender for these instead.
+ mMedia->GetTransmitPipelinesMatching(aSelector, &sendPipelines);
+ if (!sendPipelines.Length()) {
+ CSFLogError(LOGTAG, "%s: Found no pipelines matching selector.",
+ __FUNCTION__);
+ }
+
+ for (const auto& pipeline : sendPipelines) {
+ promises.AppendElement(GetSenderStats(pipeline));
+ }
+
+ for (const auto& transceiver : mMedia->GetTransceivers()) {
+ if (transceiver->Receiver()->HasTrack(aSelector)) {
+ // Right now, returns two promises; one for RTP/RTCP stats, and another
+ // for ICE stats.
+ promises.AppendElements(transceiver->Receiver()->GetStatsInternal());
+ }
+ }
+
+ // TODO(bug 1616937): We need to move this is RTCRtpSender, to make
+ // getStats on those objects work properly. It might be worth optimizing the
+ // null selector case, so we don't end up with bunches of copies of the same
+ // transport information in the final report.
+ if (aSelector) {
+ std::string transportId =
+ mMedia->GetTransportIdMatchingSendTrack(*aSelector);
+ if (!transportId.empty()) {
+ promises.AppendElement(
+ mTransportHandler->GetIceStats(transportId, now));
+ }
+ } else {
+ promises.AppendElement(mTransportHandler->GetIceStats("", now));
+ }
+
+ promises.AppendElement(GetDataChannelStats(mDataConnection, now));
+ }
+
+ // This is what we're going to return; all the stuff in |promises| will be
+ // accumulated here.
+ UniquePtr<dom::RTCStatsReportInternal> report(
+ new dom::RTCStatsReportInternal);
+ report->mPcid = NS_ConvertASCIItoUTF16(mName.c_str());
+ report->mBrowserId = mWindow->GetBrowsingContext()->BrowserId();
+ report->mConfiguration.Construct(mJsConfiguration);
+ // TODO(bug 1589416): We need to do better here.
+ if (!mIceStartTime.IsNull()) {
+ report->mCallDurationMs.Construct(
+ (TimeStamp::Now() - mIceStartTime).ToMilliseconds());
+ }
+ report->mIceRestarts = mIceRestartCount;
+ report->mIceRollbacks = mIceRollbackCount;
+ report->mClosed = false;
+ report->mTimestamp = now;
+
+ if (aInternalStats && mJsepSession) {
+ for (const auto& candidate : mRawTrickledCandidates) {
+ if (!report->mRawRemoteCandidates.AppendElement(
+ NS_ConvertASCIItoUTF16(candidate.c_str()), fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ if (mJsepSession) {
+ // TODO we probably should report Current and Pending SDPs here
+ // separately. Plus the raw SDP we got from JS (mLocalRequestedSDP).
+ // And if it's the offer or answer would also be nice.
+ std::string localDescription =
+ mJsepSession->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
+ std::string remoteDescription =
+ mJsepSession->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
+ report->mLocalSdp.Construct(
+ NS_ConvertASCIItoUTF16(localDescription.c_str()));
+ report->mRemoteSdp.Construct(
+ NS_ConvertASCIItoUTF16(remoteDescription.c_str()));
+ if (!report->mSdpHistory.AppendElements(mSdpHistory, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ if (mJsepSession->IsPendingOfferer().isSome()) {
+ report->mOfferer.Construct(*mJsepSession->IsPendingOfferer());
+ } else if (mJsepSession->IsCurrentOfferer().isSome()) {
+ report->mOfferer.Construct(*mJsepSession->IsCurrentOfferer());
+ } else {
+ // Silly.
+ report->mOfferer.Construct(false);
+ }
+ }
+ }
+
+ return dom::RTCStatsPromise::All(mThread, promises)
+ ->Then(
+ mThread, __func__,
+ [report = std::move(report), idGen = mIdGenerator](
+ const nsTArray<UniquePtr<dom::RTCStatsCollection>>&
+ aStats) mutable {
+ // Rewrite an Optional id
+ auto rewriteId = [&idGen](Optional<nsString>& id) {
+ if (id.WasPassed()) {
+ id.Value() = idGen->Id(id.Value());
+ }
+ };
+
+ // Involves a lot of copying, since webidl dictionaries don't have
+ // move semantics. Oh well.
+ for (const auto& stats : aStats) {
+ for (auto& stat : stats->mIceCandidatePairStats) {
+ rewriteId(stat.mLocalCandidateId);
+ rewriteId(stat.mRemoteCandidateId);
+ };
+ AssignWithOpaqueIds(stats->mIceCandidatePairStats,
+ report->mIceCandidatePairStats, idGen);
+
+ AssignWithOpaqueIds(stats->mIceCandidateStats,
+ report->mIceCandidateStats, idGen);
+
+ RewriteRemoteIds(stats->mInboundRtpStreamStats, idGen);
+ AssignWithOpaqueIds(stats->mInboundRtpStreamStats,
+ report->mInboundRtpStreamStats, idGen);
+
+ RewriteRemoteIds(stats->mOutboundRtpStreamStats, idGen);
+ AssignWithOpaqueIds(stats->mOutboundRtpStreamStats,
+ report->mOutboundRtpStreamStats, idGen);
+
+ RewriteLocalIds(stats->mRemoteInboundRtpStreamStats, idGen);
+ AssignWithOpaqueIds(stats->mRemoteInboundRtpStreamStats,
+ report->mRemoteInboundRtpStreamStats, idGen);
+
+ RewriteLocalIds(stats->mRemoteOutboundRtpStreamStats, idGen);
+ AssignWithOpaqueIds(stats->mRemoteOutboundRtpStreamStats,
+ report->mRemoteOutboundRtpStreamStats, idGen);
+
+ AssignWithOpaqueIds(stats->mRtpContributingSourceStats,
+ report->mRtpContributingSourceStats, idGen);
+ AssignWithOpaqueIds(stats->mTrickledIceCandidateStats,
+ report->mTrickledIceCandidateStats, idGen);
+ AssignWithOpaqueIds(stats->mDataChannelStats,
+ report->mDataChannelStats, idGen);
+ if (!report->mRawLocalCandidates.AppendElements(
+ stats->mRawLocalCandidates, fallible) ||
+ !report->mRawRemoteCandidates.AppendElements(
+ stats->mRawRemoteCandidates, fallible) ||
+ !report->mVideoFrameHistories.AppendElements(
+ stats->mVideoFrameHistories, fallible) ||
+ !report->mBandwidthEstimations.AppendElements(
+ stats->mBandwidthEstimations, fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which
+ // might involve multiple reallocations) and potentially
+ // crashing here, SetCapacity could be called outside the loop
+ // once.
+ mozalloc_handle_oom(0);
+ }
+ }
+ return dom::RTCStatsReportPromise::CreateAndResolve(
+ std::move(report), __func__);
+ },
+ [](nsresult rv) {
+ return dom::RTCStatsReportPromise::CreateAndReject(rv, __func__);
+ });
+}
+
+void PeerConnectionImpl::RecordLongtermICEStatistics() {
+ WebrtcGlobalInformation::StoreLongTermICEStatistics(*this);
+}
+
+void PeerConnectionImpl::RecordIceRestartStatistics(JsepSdpType type) {
+ switch (type) {
+ case mozilla::kJsepSdpOffer:
+ case mozilla::kJsepSdpPranswer:
+ break;
+ case mozilla::kJsepSdpAnswer:
+ ++mIceRestartCount;
+ break;
+ case mozilla::kJsepSdpRollback:
+ ++mIceRollbackCount;
+ break;
+ }
+}
+
+void PeerConnectionImpl::StoreConfigurationForAboutWebrtc(
+ const dom::RTCConfiguration& aConfig) {
+ // This will only be called once, when the PeerConnection is initially
+ // configured, at least until setConfiguration is implemented
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1253706
+ // @TODO call this from setConfiguration
+ if (aConfig.mIceServers.WasPassed()) {
+ for (const auto& server : aConfig.mIceServers.Value()) {
+ RTCIceServerInternal internal;
+ internal.mCredentialProvided = server.mCredential.WasPassed();
+ internal.mUserNameProvided = server.mUsername.WasPassed();
+ if (server.mUrl.WasPassed()) {
+ if (!internal.mUrls.AppendElement(server.mUrl.Value(), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ if (server.mUrls.WasPassed()) {
+ for (const auto& url : server.mUrls.Value().GetAsStringSequence()) {
+ if (!internal.mUrls.AppendElement(url, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ if (!mJsConfiguration.mIceServers.AppendElement(internal, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ if (aConfig.mSdpSemantics.WasPassed()) {
+ mJsConfiguration.mSdpSemantics.Construct(aConfig.mSdpSemantics.Value());
+ }
+ }
+ }
+ mJsConfiguration.mIceTransportPolicy.Construct(aConfig.mIceTransportPolicy);
+ mJsConfiguration.mBundlePolicy.Construct(aConfig.mBundlePolicy);
+ mJsConfiguration.mPeerIdentityProvided = !aConfig.mPeerIdentity.IsEmpty();
+ mJsConfiguration.mCertificatesProvided =
+ aConfig.mCertificates.WasPassed() &&
+ !aConfig.mCertificates.Value().Length();
+}
+
+dom::Sequence<dom::RTCSdpParsingErrorInternal>
+PeerConnectionImpl::GetLastSdpParsingErrors() const {
+ const auto& sdpErrors = mJsepSession->GetLastSdpParsingErrors();
+ dom::Sequence<dom::RTCSdpParsingErrorInternal> domErrors;
+ if (!domErrors.SetCapacity(domErrors.Length(), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ for (const auto& error : sdpErrors) {
+ mozilla::dom::RTCSdpParsingErrorInternal internal;
+ internal.mLineNumber = error.first;
+ if (!AppendASCIItoUTF16(MakeStringSpan(error.second.c_str()),
+ internal.mError, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ if (!domErrors.AppendElement(std::move(internal), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ return domErrors;
+}
+
+// Telemetry for when calls start
+void PeerConnectionImpl::StartCallTelem() {
+ if (mCallTelemStarted) {
+ return;
+ }
+ MOZ_RELEASE_ASSERT(mWindow);
+ uint64_t windowId = mWindow->WindowID();
+ auto found = sCallDurationTimers.find(windowId);
+ if (found == sCallDurationTimers.end()) {
+ found =
+ sCallDurationTimers.emplace(windowId, PeerConnectionAutoTimer()).first;
+ }
+ found->second.RegisterConnection();
+ mCallTelemStarted = true;
+
+ // Increment session call counter
+ // If we want to track Loop calls independently here, we need two histograms.
+ //
+ // NOTE: As of bug 1654248 landing we are no longer counting renegotiations
+ // as separate calls. Expect numbers to drop compared to WEBRTC_CALL_COUNT_2.
+ Telemetry::Accumulate(Telemetry::WEBRTC_CALL_COUNT_3, 1);
+}
+
+std::map<uint64_t, PeerConnectionAutoTimer>
+ PeerConnectionImpl::sCallDurationTimers;
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.h b/dom/media/webrtc/jsapi/PeerConnectionImpl.h
new file mode 100644
index 0000000000..39aaff2055
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.h
@@ -0,0 +1,642 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _PEER_CONNECTION_IMPL_H_
+#define _PEER_CONNECTION_IMPL_H_
+
+#include <string>
+#include <vector>
+#include <map>
+#include <cmath>
+
+#include "prlock.h"
+#include "mozilla/RefPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIThread.h"
+#include "mozilla/Mutex.h"
+
+// Work around nasty macro in webrtc/voice_engine/voice_engine_defines.h
+#ifdef GetLastError
+# undef GetLastError
+#endif
+
+#include "jsep/JsepSession.h"
+#include "jsep/JsepSessionImpl.h"
+#include "sdp/SdpMediaSection.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/RTCPeerConnectionBinding.h" // mozPacketDumpType, maybe move?
+#include "mozilla/dom/RTCRtpTransceiverBinding.h"
+#include "mozilla/dom/RTCConfigurationBinding.h"
+#include "PrincipalChangeObserver.h"
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/net/DataChannel.h"
+#include "VideoUtils.h"
+#include "VideoSegment.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/PeerIdentity.h"
+#include "RTCStatsIdGenerator.h"
+#include "RTCStatsReport.h"
+
+namespace test {
+#ifdef USE_FAKE_PCOBSERVER
+class AFakePCObserver;
+#endif
+} // namespace test
+
+class nsDOMDataChannel;
+
+namespace mozilla {
+struct CandidateInfo;
+class DataChannel;
+class DtlsIdentity;
+class MediaPipeline;
+class MediaPipelineReceive;
+class MediaPipelineTransmit;
+class TransceiverImpl;
+
+namespace dom {
+class RTCCertificate;
+struct RTCConfiguration;
+struct RTCRtpSourceEntry;
+struct RTCIceServer;
+struct RTCOfferOptions;
+struct RTCRtpParameters;
+class RTCRtpSender;
+class MediaStreamTrack;
+
+#ifdef USE_FAKE_PCOBSERVER
+typedef test::AFakePCObserver PeerConnectionObserver;
+typedef const char* PCObserverString;
+#else
+class PeerConnectionObserver;
+typedef NS_ConvertUTF8toUTF16 PCObserverString;
+#endif
+} // namespace dom
+} // namespace mozilla
+
+#if defined(__cplusplus) && __cplusplus >= 201103L
+typedef struct Timecard Timecard;
+#else
+# include "common/time_profiling/timecard.h"
+#endif
+
+// To preserve blame, convert nsresult to ErrorResult with wrappers. These
+// macros help declare wrappers w/function being wrapped when there are no
+// differences.
+
+#define NS_IMETHODIMP_TO_ERRORRESULT(func, rv, ...) \
+ NS_IMETHODIMP func(__VA_ARGS__); \
+ void func(__VA_ARGS__, rv)
+
+#define NS_IMETHODIMP_TO_ERRORRESULT_RETREF(resulttype, func, rv, ...) \
+ NS_IMETHODIMP func(__VA_ARGS__, resulttype** result); \
+ already_AddRefed<resulttype> func(__VA_ARGS__, rv)
+
+namespace mozilla {
+
+using mozilla::DtlsIdentity;
+using mozilla::ErrorResult;
+using mozilla::PeerIdentity;
+using mozilla::dom::PeerConnectionObserver;
+using mozilla::dom::RTCConfiguration;
+using mozilla::dom::RTCIceServer;
+using mozilla::dom::RTCOfferOptions;
+
+class PeerConnectionWrapper;
+class PeerConnectionMedia;
+class RemoteSourceStreamInfo;
+
+// Uuid Generator
+class PCUuidGenerator : public mozilla::JsepUuidGenerator {
+ public:
+ virtual bool Generate(std::string* idp) override;
+
+ private:
+ nsCOMPtr<nsIUUIDGenerator> mGenerator;
+};
+
+// This is a variation of Telemetry::AutoTimer that keeps a reference
+// count and records the elapsed time when the count falls to zero. The
+// elapsed time is recorded in seconds.
+struct PeerConnectionAutoTimer {
+ PeerConnectionAutoTimer() : mRefCnt(0), mStart(TimeStamp::Now()){};
+ void RegisterConnection();
+ void UnregisterConnection();
+ bool IsStopped();
+
+ private:
+ int64_t mRefCnt;
+ TimeStamp mStart;
+};
+
+// Enter an API call and check that the state is OK,
+// the PC isn't closed, etc.
+#define PC_AUTO_ENTER_API_CALL(assert_ice_ready) \
+ do { \
+ /* do/while prevents res from conflicting with locals */ \
+ nsresult res = CheckApiState(assert_ice_ready); \
+ if (NS_FAILED(res)) return res; \
+ } while (0)
+#define PC_AUTO_ENTER_API_CALL_VOID_RETURN(assert_ice_ready) \
+ do { \
+ /* do/while prevents res from conflicting with locals */ \
+ nsresult res = CheckApiState(assert_ice_ready); \
+ if (NS_FAILED(res)) return; \
+ } while (0)
+#define PC_AUTO_ENTER_API_CALL_NO_CHECK() CheckThread()
+
+class PeerConnectionImpl final
+ : public nsISupports,
+ public mozilla::DataChannelConnection::DataConnectionListener,
+ public dom::PrincipalChangeObserver<dom::MediaStreamTrack> {
+ struct Internal; // Avoid exposing c includes to bindings
+
+ public:
+ explicit PeerConnectionImpl(
+ const mozilla::dom::GlobalObject* aGlobal = nullptr);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+
+ static already_AddRefed<PeerConnectionImpl> Constructor(
+ const mozilla::dom::GlobalObject& aGlobal);
+
+ // DataConnection observers
+ void NotifyDataChannel(already_AddRefed<mozilla::DataChannel> aChannel)
+ // PeerConnectionImpl only inherits from mozilla::DataChannelConnection
+ // inside libxul.
+ override;
+
+ const RefPtr<MediaTransportHandler> GetTransportHandler() const;
+
+ // Get the media object
+ const RefPtr<PeerConnectionMedia>& media() const {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mMedia;
+ }
+
+ // Handle system to allow weak references to be passed through C code
+ virtual const std::string& GetHandle();
+
+ // Name suitable for exposing to content
+ virtual const std::string& GetName();
+
+ // ICE events
+ void IceConnectionStateChange(dom::RTCIceConnectionState state);
+ void IceGatheringStateChange(dom::RTCIceGatheringState state);
+ void OnCandidateFound(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo);
+ void UpdateDefaultCandidate(const std::string& defaultAddr,
+ uint16_t defaultPort,
+ const std::string& defaultRtcpAddr,
+ uint16_t defaultRtcpPort,
+ const std::string& transportId);
+
+ static void ListenThread(void* aData);
+ static void ConnectThread(void* aData);
+
+ // Get the main thread
+ nsCOMPtr<nsIThread> GetMainThread() { return mThread; }
+
+ // Get the STS thread
+ nsISerialEventTarget* GetSTSThread() {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mSTSThread;
+ }
+
+ nsPIDOMWindowInner* GetWindow() const {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mWindow;
+ }
+
+ nsresult Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindowInner* aWindow,
+ const RTCConfiguration& aConfiguration,
+ nsISupports* aThread);
+
+ // Initialize PeerConnection from an RTCConfiguration object (JS entrypoint)
+ void Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindowInner& aWindow,
+ const RTCConfiguration& aConfiguration, nsISupports* aThread,
+ ErrorResult& rv);
+
+ void SetCertificate(mozilla::dom::RTCCertificate& aCertificate);
+ const RefPtr<mozilla::dom::RTCCertificate>& Certificate() const;
+ // This is a hack to support external linkage.
+ RefPtr<DtlsIdentity> Identity() const;
+
+ NS_IMETHODIMP_TO_ERRORRESULT(CreateOffer, ErrorResult& rv,
+ const RTCOfferOptions& aOptions) {
+ rv = CreateOffer(aOptions);
+ }
+
+ NS_IMETHODIMP CreateAnswer();
+ void CreateAnswer(ErrorResult& rv) { rv = CreateAnswer(); }
+
+ NS_IMETHODIMP CreateOffer(const mozilla::JsepOfferOptions& aConstraints);
+
+ NS_IMETHODIMP SetLocalDescription(int32_t aAction, const char* aSDP);
+
+ void SetLocalDescription(int32_t aAction, const nsAString& aSDP,
+ ErrorResult& rv) {
+ rv = SetLocalDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get());
+ }
+
+ NS_IMETHODIMP SetRemoteDescription(int32_t aAction, const char* aSDP);
+
+ void SetRemoteDescription(int32_t aAction, const nsAString& aSDP,
+ ErrorResult& rv) {
+ rv = SetRemoteDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get());
+ }
+
+ already_AddRefed<dom::Promise> GetStats(dom::MediaStreamTrack* aSelector);
+
+ void GetRemoteStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreamsOut) const;
+
+ NS_IMETHODIMP AddIceCandidate(const char* aCandidate, const char* aMid,
+ const char* aUfrag,
+ const dom::Nullable<unsigned short>& aLevel);
+
+ void AddIceCandidate(const nsAString& aCandidate, const nsAString& aMid,
+ const nsAString& aUfrag,
+ const dom::Nullable<unsigned short>& aLevel,
+ ErrorResult& rv) {
+ rv = AddIceCandidate(NS_ConvertUTF16toUTF8(aCandidate).get(),
+ NS_ConvertUTF16toUTF8(aMid).get(),
+ NS_ConvertUTF16toUTF8(aUfrag).get(), aLevel);
+ }
+
+ void UpdateNetworkState(bool online);
+
+ NS_IMETHODIMP CloseStreams();
+
+ void CloseStreams(ErrorResult& rv) { rv = CloseStreams(); }
+
+ already_AddRefed<TransceiverImpl> CreateTransceiverImpl(
+ const nsAString& aKind, dom::MediaStreamTrack* aSendTrack,
+ ErrorResult& rv);
+
+ bool CheckNegotiationNeeded(ErrorResult& rv);
+
+ NS_IMETHODIMP_TO_ERRORRESULT(ReplaceTrackNoRenegotiation, ErrorResult& rv,
+ TransceiverImpl& aTransceiver,
+ mozilla::dom::MediaStreamTrack* aWithTrack) {
+ rv = ReplaceTrackNoRenegotiation(aTransceiver, aWithTrack);
+ }
+
+ // test-only
+ NS_IMETHODIMP_TO_ERRORRESULT(EnablePacketDump, ErrorResult& rv,
+ unsigned long level, dom::mozPacketDumpType type,
+ bool sending) {
+ rv = EnablePacketDump(level, type, sending);
+ }
+
+ // test-only
+ NS_IMETHODIMP_TO_ERRORRESULT(DisablePacketDump, ErrorResult& rv,
+ unsigned long level, dom::mozPacketDumpType type,
+ bool sending) {
+ rv = DisablePacketDump(level, type, sending);
+ }
+
+ void GetPeerIdentity(nsAString& peerIdentity) {
+ if (mPeerIdentity) {
+ peerIdentity = mPeerIdentity->ToString();
+ return;
+ }
+
+ peerIdentity.SetIsVoid(true);
+ }
+
+ const PeerIdentity* GetPeerIdentity() const { return mPeerIdentity; }
+ NS_IMETHODIMP_TO_ERRORRESULT(SetPeerIdentity, ErrorResult& rv,
+ const nsAString& peerIdentity) {
+ rv = SetPeerIdentity(peerIdentity);
+ }
+
+ const std::string& GetIdAsAscii() const { return mName; }
+
+ void GetId(nsAString& id) { id = NS_ConvertASCIItoUTF16(mName.c_str()); }
+
+ void SetId(const nsAString& id) { mName = NS_ConvertUTF16toUTF8(id).get(); }
+
+ // this method checks to see if we've made a promise to protect media.
+ bool PrivacyRequested() const {
+ return mPrivacyRequested.isSome() && *mPrivacyRequested;
+ }
+
+ bool PrivacyNeeded() const {
+ return mPrivacyRequested.isSome() && *mPrivacyRequested;
+ }
+
+ NS_IMETHODIMP GetFingerprint(char** fingerprint);
+ void GetFingerprint(nsAString& fingerprint) {
+ char* tmp;
+ nsresult rv = GetFingerprint(&tmp);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ fingerprint.AssignASCII(tmp);
+ delete[] tmp;
+ }
+
+ void GetCurrentLocalDescription(nsAString& aSDP) const;
+ void GetPendingLocalDescription(nsAString& aSDP) const;
+
+ void GetCurrentRemoteDescription(nsAString& aSDP) const;
+ void GetPendingRemoteDescription(nsAString& aSDP) const;
+
+ dom::Nullable<bool> GetCurrentOfferer() const;
+ dom::Nullable<bool> GetPendingOfferer() const;
+
+ NS_IMETHODIMP SignalingState(mozilla::dom::RTCSignalingState* aState);
+
+ mozilla::dom::RTCSignalingState SignalingState() {
+ mozilla::dom::RTCSignalingState state;
+ SignalingState(&state);
+ return state;
+ }
+
+ NS_IMETHODIMP IceConnectionState(mozilla::dom::RTCIceConnectionState* aState);
+
+ mozilla::dom::RTCIceConnectionState IceConnectionState() {
+ mozilla::dom::RTCIceConnectionState state;
+ IceConnectionState(&state);
+ return state;
+ }
+
+ NS_IMETHODIMP IceGatheringState(mozilla::dom::RTCIceGatheringState* aState);
+
+ mozilla::dom::RTCIceGatheringState IceGatheringState() {
+ return mIceGatheringState;
+ }
+
+ NS_IMETHODIMP Close();
+
+ void Close(ErrorResult& rv) { rv = Close(); }
+
+ bool PluginCrash(uint32_t aPluginID, const nsAString& aPluginName);
+
+ void RecordEndOfCallTelemetry();
+
+ nsresult InitializeDataChannel();
+
+ NS_IMETHODIMP_TO_ERRORRESULT_RETREF(nsDOMDataChannel, CreateDataChannel,
+ ErrorResult& rv, const nsAString& aLabel,
+ const nsAString& aProtocol,
+ uint16_t aType, bool outOfOrderAllowed,
+ uint16_t aMaxTime, uint16_t aMaxNum,
+ bool aExternalNegotiated,
+ uint16_t aStream);
+
+ // Gets the RTC Signaling State of the JSEP session
+ dom::RTCSignalingState GetSignalingState() const;
+
+ void OnSetDescriptionSuccess(JsepSdpType sdpType, bool remote);
+
+ bool IsClosed() const;
+ // called when DTLS connects; we only need this once
+ nsresult OnAlpnNegotiated(bool aPrivacyRequested);
+
+ bool HasMedia() const;
+
+ // initialize telemetry for when calls start
+ void StartCallTelem();
+
+ RefPtr<dom::RTCStatsReportPromise> GetStats(dom::MediaStreamTrack* aSelector,
+ bool aInternalStats);
+
+ void RecordConduitTelemetry();
+
+ // for monitoring changes in track ownership
+ // PeerConnectionMedia can't do it because it doesn't know about principals
+ virtual void PrincipalChanged(dom::MediaStreamTrack* aTrack) override;
+
+ void OnMediaError(const std::string& aError);
+
+ bool ShouldDumpPacket(size_t level, dom::mozPacketDumpType type,
+ bool sending) const;
+
+ void DumpPacket_m(size_t level, dom::mozPacketDumpType type, bool sending,
+ UniquePtr<uint8_t[]>& packet, size_t size);
+
+ const dom::RTCStatsTimestampMaker& GetTimestampMaker() const {
+ return mTimestampMaker;
+ }
+
+ // Utility function, given a string pref and an URI, returns whether or not
+ // the URI occurs in the pref. Wildcards are supported (e.g. *.example.com)
+ // and multiple hostnames can be present, separated by commas.
+ static bool HostnameInPref(const char* aPrefList, nsIURI* aDocURI);
+
+ void StampTimecard(const char* aEvent);
+
+ private:
+ virtual ~PeerConnectionImpl();
+ PeerConnectionImpl(const PeerConnectionImpl& rhs);
+ PeerConnectionImpl& operator=(PeerConnectionImpl);
+
+ RefPtr<dom::RTCStatsPromise> GetReceiverStats(
+ const RefPtr<MediaPipelineReceive>& aPipeline);
+ RefPtr<dom::RTCStatsPromise> GetSenderStats(
+ const RefPtr<MediaPipelineTransmit>& aPipeline);
+ RefPtr<dom::RTCStatsPromise> GetDataChannelStats(
+ const RefPtr<DataChannelConnection>& aDataChannelConnection,
+ const DOMHighResTimeStamp aTimestamp);
+ nsresult CalculateFingerprint(const std::string& algorithm,
+ std::vector<uint8_t>* fingerprint) const;
+ nsresult ConfigureJsepSessionCodecs();
+
+ NS_IMETHODIMP EnsureDataConnection(uint16_t aLocalPort, uint16_t aNumstreams,
+ uint32_t aMaxMessageSize, bool aMMSSet);
+
+ nsresult CloseInt();
+ nsresult CheckApiState(bool assert_ice_ready) const;
+ void CheckThread() const { MOZ_ASSERT(CheckThreadInt(), "Wrong thread"); }
+ bool CheckThreadInt() const {
+ bool on;
+ NS_ENSURE_SUCCESS(mThread->IsOnCurrentThread(&on), false);
+ NS_ENSURE_TRUE(on, false);
+ return true;
+ }
+
+ // test-only: called from AddRIDExtension and AddRIDFilter
+ // for simulcast mochitests.
+ RefPtr<MediaPipeline> GetMediaPipelineForTrack(
+ dom::MediaStreamTrack& aRecvTrack);
+
+ // Shut down media - called on main thread only
+ void ShutdownMedia();
+
+ void CandidateReady(const std::string& candidate,
+ const std::string& transportId, const std::string& ufrag);
+ void SendLocalIceCandidateToContent(uint16_t level, const std::string& mid,
+ const std::string& candidate,
+ const std::string& ufrag);
+
+ nsresult GetDatachannelParameters(uint32_t* channels, uint16_t* localport,
+ uint16_t* remoteport,
+ uint32_t* maxmessagesize, bool* mmsset,
+ std::string* transportId,
+ bool* client) const;
+
+ nsresult AddRtpTransceiverToJsepSession(RefPtr<JsepTransceiver>& transceiver);
+ already_AddRefed<TransceiverImpl> CreateTransceiverImpl(
+ JsepTransceiver* aJsepTransceiver, dom::MediaStreamTrack* aSendTrack,
+ ErrorResult& aRv);
+
+ // When ICE completes, we record a bunch of statistics that outlive the
+ // PeerConnection. This is just telemetry right now, but this can also
+ // include things like dumping the RLogConnector somewhere, saving away
+ // an RTCStatsReport somewhere so it can be inspected after the call is over,
+ // or other things.
+ void RecordLongtermICEStatistics();
+
+ void RecordIceRestartStatistics(JsepSdpType type);
+
+ void StoreConfigurationForAboutWebrtc(const RTCConfiguration& aConfig);
+
+ dom::Sequence<dom::RTCSdpParsingErrorInternal> GetLastSdpParsingErrors()
+ const;
+ // Timecard used to measure processing time. This should be the first class
+ // attribute so that we accurately measure the time required to instantiate
+ // any other attributes of this class.
+ Timecard* mTimeCard;
+
+ // Configuration used to initialize the PeerConnection
+ dom::RTCConfigurationInternal mJsConfiguration;
+
+ mozilla::dom::RTCSignalingState mSignalingState;
+
+ // ICE State
+ mozilla::dom::RTCIceConnectionState mIceConnectionState;
+ mozilla::dom::RTCIceGatheringState mIceGatheringState;
+
+ nsCOMPtr<nsIThread> mThread;
+ RefPtr<PeerConnectionObserver> mPCObserver;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+ // The SDP sent in from JS
+ std::string mLocalRequestedSDP;
+ std::string mRemoteRequestedSDP;
+ // Only accessed from main
+ mozilla::dom::Sequence<mozilla::dom::RTCSdpHistoryEntryInternal> mSdpHistory;
+ std::string mPendingLocalDescription;
+ std::string mPendingRemoteDescription;
+ std::string mCurrentLocalDescription;
+ std::string mCurrentRemoteDescription;
+ Maybe<bool> mPendingOfferer;
+ Maybe<bool> mCurrentOfferer;
+
+ // DTLS fingerprint
+ std::string mFingerprint;
+ std::string mRemoteFingerprint;
+
+ // identity-related fields
+ // The entity on the other end of the peer-to-peer connection;
+ // void if they are not yet identified, and no identity setting has been set
+ RefPtr<PeerIdentity> mPeerIdentity;
+ // The certificate we are using.
+ RefPtr<mozilla::dom::RTCCertificate> mCertificate;
+ // 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
+ Maybe<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<nsISerialEventTarget> mSTSThread;
+
+ // DataConnection that's used to get all the DataChannels
+ RefPtr<mozilla::DataChannelConnection> mDataConnection;
+
+ bool mForceIceTcp;
+ RefPtr<PeerConnectionMedia> mMedia;
+ RefPtr<MediaTransportHandler> mTransportHandler;
+
+ // The JSEP negotiation session.
+ mozilla::UniquePtr<PCUuidGenerator> mUuidGen;
+ mozilla::UniquePtr<mozilla::JsepSession> mJsepSession;
+ unsigned long mIceRestartCount;
+ unsigned long mIceRollbackCount;
+
+ // The following are used for Telemetry:
+ bool mCallTelemStarted = false;
+ bool mCallTelemEnded = false;
+
+ // Start time of ICE.
+ mozilla::TimeStamp mIceStartTime;
+ // Hold PeerConnectionAutoTimer instances for each window.
+ static std::map<uint64_t, PeerConnectionAutoTimer> sCallDurationTimers;
+
+ bool mHaveConfiguredCodecs;
+
+ bool mTrickle;
+
+ bool mPrivateWindow;
+
+ // Whether this PeerConnection is being counted as active by mWindow
+ bool mActiveOnWindow;
+
+ // storage for Telemetry data
+ uint16_t mMaxReceiving[SdpMediaSection::kMediaTypes];
+ uint16_t mMaxSending[SdpMediaSection::kMediaTypes];
+
+ std::vector<unsigned> mSendPacketDumpFlags;
+ std::vector<unsigned> mRecvPacketDumpFlags;
+ Atomic<bool> mPacketDumpEnabled;
+ mutable Mutex mPacketDumpFlagsMutex;
+
+ // used to store the raw trickle candidate string for display
+ // on the about:webrtc raw candidates table.
+ std::vector<std::string> mRawTrickledCandidates;
+
+ dom::RTCStatsTimestampMaker mTimestampMaker;
+
+ RefPtr<RTCStatsIdGenerator> mIdGenerator;
+ // Ordinarily, I would use a std::map here, but this used to be a JS Map
+ // which iterates in insertion order, and I want to avoid changing this.
+ nsTArray<RefPtr<DOMMediaStream>> mReceiveStreams;
+
+ DOMMediaStream* GetReceiveStream(const std::string& aId) const;
+ DOMMediaStream* CreateReceiveStream(const std::string& aId);
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ bool mRtxIsAllowed = true;
+
+ public:
+ // these are temporary until the DataChannel Listen/Connect API is removed
+ unsigned short listenPort;
+ unsigned short connectPort;
+ char* connectStr; // XXX ownership/free
+};
+
+// This is what is returned when you acquire on a handle
+class PeerConnectionWrapper {
+ public:
+ explicit PeerConnectionWrapper(const std::string& handle);
+
+ PeerConnectionImpl* impl() { return impl_; }
+
+ private:
+ RefPtr<PeerConnectionImpl> impl_;
+};
+
+} // namespace mozilla
+
+#undef NS_IMETHODIMP_TO_ERRORRESULT
+#undef NS_IMETHODIMP_TO_ERRORRESULT_RETREF
+#endif // _PEER_CONNECTION_IMPL_H_
diff --git a/dom/media/webrtc/jsapi/PeerConnectionMedia.cpp b/dom/media/webrtc/jsapi/PeerConnectionMedia.cpp
new file mode 100644
index 0000000000..eb3b6df173
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PeerConnectionMedia.cpp
@@ -0,0 +1,891 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "common/browser_logging/CSFLog.h"
+
+#include "transport/nr_socket_proxy_config.h"
+#include "transportbridge/MediaPipelineFilter.h"
+#include "transportbridge/MediaPipeline.h"
+#include "PeerConnectionImpl.h"
+#include "PeerConnectionMedia.h"
+#include "RTCDtlsTransport.h"
+#include "transport/runnable_utils.h"
+#include "jsep/JsepSession.h"
+#include "jsep/JsepTransport.h"
+
+#include "nsContentUtils.h"
+#include "nsIIDNService.h"
+#include "nsILoadInfo.h"
+#include "nsIProxyInfo.h"
+#include "nsIPrincipal.h"
+#include "mozilla/LoadInfo.h"
+#include "nsIProxiedChannel.h"
+
+#include "nsIScriptGlobalObject.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/WebrtcProxyConfig.h"
+#include "MediaManager.h"
+#include "libwebrtcglue/WebrtcGmpVideoCodec.h"
+
+namespace mozilla {
+using namespace dom;
+
+static const char* pcmLogTag = "PeerConnectionMedia";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG pcmLogTag
+
+void PeerConnectionMedia::StunAddrsHandler::OnMDNSQueryComplete(
+ const nsCString& hostname, const Maybe<nsCString>& address) {
+ ASSERT_ON_THREAD(pcm_->mMainThread);
+ auto itor = pcm_->mQueriedMDNSHostnames.find(hostname.BeginReading());
+ if (itor != pcm_->mQueriedMDNSHostnames.end()) {
+ if (address) {
+ for (auto& cand : itor->second) {
+ // Replace obfuscated address with actual address
+ std::string obfuscatedAddr = cand.mTokenizedCandidate[4];
+ cand.mTokenizedCandidate[4] = address->BeginReading();
+ std::ostringstream o;
+ for (size_t i = 0; i < cand.mTokenizedCandidate.size(); ++i) {
+ o << cand.mTokenizedCandidate[i];
+ if (i + 1 != cand.mTokenizedCandidate.size()) {
+ o << " ";
+ }
+ }
+ std::string mungedCandidate = o.str();
+ pcm_->mParent->StampTimecard("Done looking up mDNS name");
+ pcm_->mTransportHandler->AddIceCandidate(
+ cand.mTransportId, mungedCandidate, cand.mUfrag, obfuscatedAddr);
+ }
+ } else {
+ pcm_->mParent->StampTimecard("Failed looking up mDNS name");
+ }
+ pcm_->mQueriedMDNSHostnames.erase(itor);
+ }
+}
+
+void PeerConnectionMedia::StunAddrsHandler::OnStunAddrsAvailable(
+ const mozilla::net::NrIceStunAddrArray& addrs) {
+ CSFLogInfo(LOGTAG, "%s: receiving (%d) stun addrs", __FUNCTION__,
+ (int)addrs.Length());
+ if (pcm_) {
+ pcm_->mStunAddrs = addrs.Clone();
+ pcm_->mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE;
+ pcm_->FlushIceCtxOperationQueueIfReady();
+ // If parent process returns 0 STUN addresses, change ICE connection
+ // state to failed.
+ if (!pcm_->mStunAddrs.Length()) {
+ pcm_->IceConnectionStateChange_m(dom::RTCIceConnectionState::Failed);
+ }
+ }
+}
+
+PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl* parent)
+ : mTransportHandler(parent->GetTransportHandler()),
+ mParent(parent),
+ mParentHandle(parent->GetHandle()),
+ mParentName(parent->GetName()),
+ mMainThread(mParent->GetMainThread()),
+ mSTSThread(mParent->GetSTSThread()),
+ mForceProxy(false),
+ mStunAddrsRequest(nullptr),
+ mLocalAddrsRequestState(STUN_ADDR_REQUEST_NONE),
+ mTargetForDefaultLocalAddressLookupIsSet(false),
+ mDestroyed(false) {
+ if (XRE_IsContentProcess()) {
+ nsCOMPtr<nsISerialEventTarget> target =
+ mParent->GetWindow()
+ ? mParent->GetWindow()->EventTargetFor(TaskCategory::Other)
+ : nullptr;
+
+ mStunAddrsRequest =
+ new net::StunAddrsRequestChild(new StunAddrsHandler(this), target);
+ }
+}
+
+PeerConnectionMedia::~PeerConnectionMedia() {
+ MOZ_RELEASE_ASSERT(!mMainThread);
+}
+
+void PeerConnectionMedia::InitLocalAddrs() {
+ if (mLocalAddrsRequestState == STUN_ADDR_REQUEST_PENDING) {
+ return;
+ }
+ if (mStunAddrsRequest) {
+ mLocalAddrsRequestState = STUN_ADDR_REQUEST_PENDING;
+ mStunAddrsRequest->SendGetStunAddrs();
+ } else {
+ mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE;
+ }
+}
+
+bool PeerConnectionMedia::ShouldForceProxy() const {
+ if (Preferences::GetBool("media.peerconnection.ice.proxy_only", false)) {
+ return true;
+ }
+
+ if (!Preferences::GetBool(
+ "media.peerconnection.ice.proxy_only_if_behind_proxy", false)) {
+ return false;
+ }
+
+ // Ok, we're supposed to be proxy_only, but only if a proxy is configured.
+ // Let's just see if the document was loaded via a proxy.
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = GetChannel();
+ if (!httpChannelInternal) {
+ return false;
+ }
+
+ nsCOMPtr<nsIProxiedChannel> proxiedChannel =
+ do_QueryInterface(httpChannelInternal);
+ if (!proxiedChannel) {
+ return false;
+ }
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ proxiedChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
+ if (!proxyInfo) {
+ return false;
+ }
+
+ nsCString proxyType;
+ proxyInfo->GetType(proxyType);
+
+ return !proxyType.IsEmpty() && !proxyType.EqualsLiteral("direct");
+}
+
+nsresult PeerConnectionMedia::Init() {
+ mForceProxy = ShouldForceProxy();
+
+ // setup the stun local addresses IPC async call
+ InitLocalAddrs();
+
+ ConnectSignals();
+ return NS_OK;
+}
+
+void PeerConnectionMedia::EnsureTransports(const JsepSession& aSession) {
+ for (const auto& [id, transceiver] : aSession.GetTransceivers()) {
+ (void)id; // Lame, but no better way to do this right now.
+ if (transceiver->HasOwnTransport()) {
+ mTransportHandler->EnsureProvisionalTransport(
+ transceiver->mTransport.mTransportId,
+ transceiver->mTransport.mLocalUfrag,
+ transceiver->mTransport.mLocalPwd,
+ transceiver->mTransport.mComponents);
+ }
+ }
+
+ GatherIfReady();
+}
+
+void PeerConnectionMedia::UpdateRTCDtlsTransports(bool aMarkAsStable) {
+ for (auto& transceiver : mTransceivers) {
+ std::string transportId = transceiver->GetTransportId();
+ if (transportId.empty()) {
+ continue;
+ }
+ if (!mTransportIdToRTCDtlsTransport.count(transportId)) {
+ mTransportIdToRTCDtlsTransport.emplace(
+ transportId, new RTCDtlsTransport(transceiver->GetParentObject()));
+ }
+
+ transceiver->SetDtlsTransport(mTransportIdToRTCDtlsTransport[transportId],
+ aMarkAsStable);
+ }
+}
+
+void PeerConnectionMedia::RollbackRTCDtlsTransports() {
+ for (auto& transceiver : mTransceivers) {
+ transceiver->RollbackToStableDtlsTransport();
+ }
+}
+
+void PeerConnectionMedia::RemoveRTCDtlsTransportsExcept(
+ const std::set<std::string>& aTransportIds) {
+ for (auto iter = mTransportIdToRTCDtlsTransport.begin();
+ iter != mTransportIdToRTCDtlsTransport.end();) {
+ if (!aTransportIds.count(iter->first)) {
+ iter = mTransportIdToRTCDtlsTransport.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+nsresult PeerConnectionMedia::UpdateTransports(const JsepSession& aSession,
+ const bool forceIceTcp) {
+ std::set<std::string> finalTransports;
+ for (const auto& [id, transceiver] : aSession.GetTransceivers()) {
+ (void)id; // Lame, but no better way to do this right now.
+ if (transceiver->HasOwnTransport()) {
+ finalTransports.insert(transceiver->mTransport.mTransportId);
+ UpdateTransport(*transceiver, forceIceTcp);
+ }
+ }
+
+ // clean up the unused RTCDtlsTransports
+ RemoveRTCDtlsTransportsExcept(finalTransports);
+
+ mTransportHandler->RemoveTransportsExcept(finalTransports);
+
+ for (const auto& transceiverImpl : mTransceivers) {
+ transceiverImpl->UpdateTransport();
+ }
+
+ return NS_OK;
+}
+
+void PeerConnectionMedia::UpdateTransport(const JsepTransceiver& aTransceiver,
+ bool aForceIceTcp) {
+ std::string ufrag;
+ std::string pwd;
+ std::vector<std::string> candidates;
+ size_t components = 0;
+
+ const JsepTransport& transport = aTransceiver.mTransport;
+ unsigned level = aTransceiver.GetLevel();
+
+ CSFLogDebug(LOGTAG, "ACTIVATING TRANSPORT! - PC %s: level=%u components=%u",
+ mParentHandle.c_str(), (unsigned)level,
+ (unsigned)transport.mComponents);
+
+ ufrag = transport.mIce->GetUfrag();
+ pwd = transport.mIce->GetPassword();
+ candidates = transport.mIce->GetCandidates();
+ components = transport.mComponents;
+ if (aForceIceTcp) {
+ candidates.erase(
+ std::remove_if(candidates.begin(), candidates.end(),
+ [](const std::string& s) {
+ return s.find(" UDP ") != std::string::npos ||
+ s.find(" udp ") != std::string::npos;
+ }),
+ candidates.end());
+ }
+
+ nsTArray<uint8_t> keyDer;
+ nsTArray<uint8_t> certDer;
+ nsresult rv = mParent->Identity()->Serialize(&keyDer, &certDer);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: Failed to serialize DTLS identity: %d",
+ __FUNCTION__, (int)rv);
+ return;
+ }
+
+ DtlsDigestList digests;
+ for (const auto& fingerprint :
+ transport.mDtls->GetFingerprints().mFingerprints) {
+ std::ostringstream ss;
+ ss << fingerprint.hashFunc;
+ digests.emplace_back(ss.str(), fingerprint.fingerprint);
+ }
+
+ mTransportHandler->ActivateTransport(
+ transport.mTransportId, transport.mLocalUfrag, transport.mLocalPwd,
+ components, ufrag, pwd, keyDer, certDer, mParent->Identity()->auth_type(),
+ transport.mDtls->GetRole() == JsepDtlsTransport::kJsepDtlsClient, digests,
+ mParent->PrivacyRequested());
+
+ for (auto& candidate : candidates) {
+ AddIceCandidate("candidate:" + candidate, transport.mTransportId, ufrag);
+ }
+}
+
+nsresult PeerConnectionMedia::UpdateMediaPipelines() {
+ // The GMP code is all the way on the other side of webrtc.org, and it is not
+ // feasible to plumb error information all the way back. So, we set up a
+ // handle to the PC (for the duration of this call) in a global variable.
+ // This allows the GMP code to report errors to the PC.
+ WebrtcGmpPCHandleSetter setter(mParentHandle);
+
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ transceiver->ResetSync();
+ }
+
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (!transceiver->IsVideo()) {
+ nsresult rv = transceiver->SyncWithMatchingVideoConduits(mTransceivers);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ nsresult rv = transceiver->UpdateConduit();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+void PeerConnectionMedia::StartIceChecks(const JsepSession& aSession) {
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (!mCanRegisterMDNSHostnamesDirectly) {
+ for (auto& pair : mMDNSHostnamesToRegister) {
+ mRegisteredMDNSHostnames.insert(pair.first);
+ mStunAddrsRequest->SendRegisterMDNSHostname(
+ nsCString(pair.first.c_str()), nsCString(pair.second.c_str()));
+ }
+ mMDNSHostnamesToRegister.clear();
+ mCanRegisterMDNSHostnamesDirectly = true;
+ }
+
+ std::vector<std::string> attributes;
+ if (aSession.RemoteIsIceLite()) {
+ attributes.push_back("ice-lite");
+ }
+
+ if (!aSession.GetIceOptions().empty()) {
+ attributes.push_back("ice-options:");
+ for (const auto& option : aSession.GetIceOptions()) {
+ attributes.back() += option + ' ';
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> runnable(
+ WrapRunnable(mTransportHandler, &MediaTransportHandler::StartIceChecks,
+ aSession.IsIceControlling(), attributes));
+
+ PerformOrEnqueueIceCtxOperation(runnable);
+}
+
+bool PeerConnectionMedia::GetPrefDefaultAddressOnly() const {
+ ASSERT_ON_THREAD(mMainThread); // will crash on STS thread
+
+ uint64_t winId = mParent->GetWindow()->WindowID();
+
+ bool default_address_only = Preferences::GetBool(
+ "media.peerconnection.ice.default_address_only", false);
+ default_address_only |=
+ !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId);
+ return default_address_only;
+}
+
+bool PeerConnectionMedia::GetPrefObfuscateHostAddresses() const {
+ ASSERT_ON_THREAD(mMainThread); // will crash on STS thread
+
+ uint64_t winId = mParent->GetWindow()->WindowID();
+
+ bool obfuscate_host_addresses = Preferences::GetBool(
+ "media.peerconnection.ice.obfuscate_host_addresses", false);
+ obfuscate_host_addresses &=
+ !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId);
+ obfuscate_host_addresses &= !PeerConnectionImpl::HostnameInPref(
+ "media.peerconnection.ice.obfuscate_host_addresses.blocklist",
+ mParent->GetWindow()->GetDocumentURI());
+ obfuscate_host_addresses &= XRE_IsContentProcess();
+
+ return obfuscate_host_addresses;
+}
+
+void PeerConnectionMedia::ConnectSignals() {
+ mTransportHandler->SignalGatheringStateChange.connect(
+ this, &PeerConnectionMedia::IceGatheringStateChange_s);
+ mTransportHandler->SignalConnectionStateChange.connect(
+ this, &PeerConnectionMedia::IceConnectionStateChange_s);
+ mTransportHandler->SignalCandidate.connect(
+ this, &PeerConnectionMedia::OnCandidateFound_s);
+ mTransportHandler->SignalAlpnNegotiated.connect(
+ this, &PeerConnectionMedia::AlpnNegotiated_s);
+}
+
+void PeerConnectionMedia::AddIceCandidate(const std::string& aCandidate,
+ const std::string& aTransportId,
+ const std::string& aUfrag) {
+ ASSERT_ON_THREAD(mMainThread);
+ MOZ_ASSERT(!aTransportId.empty());
+
+ bool obfuscate_host_addresses = Preferences::GetBool(
+ "media.peerconnection.ice.obfuscate_host_addresses", false);
+
+ if (obfuscate_host_addresses) {
+ std::vector<std::string> tokens;
+ TokenizeCandidate(aCandidate, tokens);
+
+ if (tokens.size() > 4) {
+ std::string addr = tokens[4];
+
+ // Check for address ending with .local
+ size_t nPeriods = std::count(addr.begin(), addr.end(), '.');
+ size_t dotLocalLength = 6; // length of ".local"
+
+ if (nPeriods == 1 &&
+ addr.rfind(".local") + dotLocalLength == addr.length()) {
+ if (mStunAddrsRequest) {
+ PendingIceCandidate cand;
+ cand.mTokenizedCandidate = std::move(tokens);
+ cand.mTransportId = aTransportId;
+ cand.mUfrag = aUfrag;
+ mQueriedMDNSHostnames[addr].push_back(cand);
+
+ mMainThread->Dispatch(NS_NewRunnableFunction(
+ "PeerConnectionMedia::SendQueryMDNSHostname",
+ [self = RefPtr<PeerConnectionMedia>(this), addr]() mutable {
+ if (self->mStunAddrsRequest) {
+ self->mParent->StampTimecard("Look up mDNS name");
+ self->mStunAddrsRequest->SendQueryMDNSHostname(
+ nsCString(nsAutoCString(addr.c_str())));
+ }
+ NS_ReleaseOnMainThread(
+ "PeerConnectionMedia::SendQueryMDNSHostname",
+ self.forget());
+ }));
+ }
+ // TODO: Bug 1535690, we don't want to tell the ICE context that remote
+ // trickle is done if we are waiting to resolve a mDNS candidate.
+ return;
+ }
+ }
+ }
+
+ mTransportHandler->AddIceCandidate(aTransportId, aCandidate, aUfrag, "");
+}
+
+void PeerConnectionMedia::UpdateNetworkState(bool online) {
+ mTransportHandler->UpdateNetworkState(online);
+}
+
+void PeerConnectionMedia::FlushIceCtxOperationQueueIfReady() {
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (IsIceCtxReady()) {
+ for (auto& queuedIceCtxOperation : mQueuedIceCtxOperations) {
+ queuedIceCtxOperation->Run();
+ }
+ mQueuedIceCtxOperations.clear();
+ }
+}
+
+void PeerConnectionMedia::PerformOrEnqueueIceCtxOperation(
+ nsIRunnable* runnable) {
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (IsIceCtxReady()) {
+ runnable->Run();
+ } else {
+ mQueuedIceCtxOperations.push_back(runnable);
+ }
+}
+
+void PeerConnectionMedia::GatherIfReady() {
+ ASSERT_ON_THREAD(mMainThread);
+
+ // Init local addrs here so that if we re-gather after an ICE restart
+ // resulting from changing WiFi networks, we get new local addrs.
+ // Otherwise, we would reuse the addrs from the original WiFi network
+ // and the ICE restart will fail.
+ if (!mStunAddrs.Length()) {
+ InitLocalAddrs();
+ }
+
+ // If we had previously queued gathering or ICE start, unqueue them
+ mQueuedIceCtxOperations.clear();
+ nsCOMPtr<nsIRunnable> runnable(WrapRunnable(
+ RefPtr<PeerConnectionMedia>(this),
+ &PeerConnectionMedia::EnsureIceGathering, GetPrefDefaultAddressOnly(),
+ GetPrefObfuscateHostAddresses()));
+
+ PerformOrEnqueueIceCtxOperation(runnable);
+}
+
+already_AddRefed<nsIHttpChannelInternal> PeerConnectionMedia::GetChannel()
+ const {
+ Document* doc = mParent->GetWindow()->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ NS_WARNING("Unable to get document from window");
+ return nullptr;
+ }
+
+ if (!doc->GetDocumentURI()->SchemeIs("file")) {
+ nsIChannel* channel = doc->GetChannel();
+ if (!channel) {
+ NS_WARNING("Unable to get channel from document");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
+ do_QueryInterface(channel);
+ if (NS_WARN_IF(!httpChannelInternal)) {
+ CSFLogInfo(LOGTAG, "%s: Document does not have an HTTP channel",
+ __FUNCTION__);
+ return nullptr;
+ }
+ return httpChannelInternal.forget();
+ }
+ return nullptr;
+}
+
+nsresult PeerConnectionMedia::SetTargetForDefaultLocalAddressLookup() {
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = GetChannel();
+ if (!httpChannelInternal) {
+ return NS_OK;
+ }
+
+ nsCString remoteIp;
+ nsresult rv = httpChannelInternal->GetRemoteAddress(remoteIp);
+ if (NS_FAILED(rv) || remoteIp.IsEmpty()) {
+ CSFLogError(LOGTAG, "%s: Failed to get remote IP address: %d", __FUNCTION__,
+ (int)rv);
+ return rv;
+ }
+
+ int32_t remotePort;
+ rv = httpChannelInternal->GetRemotePort(&remotePort);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: Failed to get remote port number: %d",
+ __FUNCTION__, (int)rv);
+ return rv;
+ }
+
+ mTransportHandler->SetTargetForDefaultLocalAddressLookup(remoteIp.get(),
+ remotePort);
+
+ return NS_OK;
+}
+
+void PeerConnectionMedia::EnsureIceGathering(bool aDefaultRouteOnly,
+ bool aObfuscateHostAddresses) {
+ auto proxyConfig = GetProxyConfig();
+ if (proxyConfig) {
+ // Note that this could check if PrivacyRequested() is set on the PC and
+ // remove "webrtc" from the ALPN list. But that would only work if the PC
+ // was constructed with a peerIdentity constraint, not when isolated
+ // streams are added. If we ever need to signal to the proxy that the
+ // media is isolated, then we would need to restructure this code.
+ mTransportHandler->SetProxyConfig(std::move(*proxyConfig));
+ }
+
+ if (!mTargetForDefaultLocalAddressLookupIsSet) {
+ nsresult rv = SetTargetForDefaultLocalAddressLookup();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unable to set target for default local address lookup");
+ }
+ mTargetForDefaultLocalAddressLookupIsSet = true;
+ }
+
+ // Make sure we don't call StartIceGathering if we're in e10s mode
+ // and we received no STUN addresses from the parent process. In the
+ // absence of previously provided STUN addresses, StartIceGathering will
+ // attempt to gather them (as in non-e10s mode), and this will cause a
+ // sandboxing exception in e10s mode.
+ if (!mStunAddrs.Length() && XRE_IsContentProcess()) {
+ CSFLogInfo(LOGTAG, "%s: No STUN addresses returned from parent process",
+ __FUNCTION__);
+ return;
+ }
+
+ mTransportHandler->StartIceGathering(aDefaultRouteOnly,
+ aObfuscateHostAddresses, mStunAddrs);
+}
+
+void PeerConnectionMedia::SelfDestruct() {
+ ASSERT_ON_THREAD(mMainThread);
+
+ CSFLogDebug(LOGTAG, "%s: ", __FUNCTION__);
+
+ mDestroyed = true;
+
+ if (mStunAddrsRequest) {
+ for (auto& hostname : mRegisteredMDNSHostnames) {
+ mStunAddrsRequest->SendUnregisterMDNSHostname(
+ nsCString(hostname.c_str()));
+ }
+ mRegisteredMDNSHostnames.clear();
+ mStunAddrsRequest->Cancel();
+ mStunAddrsRequest = nullptr;
+ }
+
+ for (auto& transceiver : mTransceivers) {
+ // transceivers are garbage-collected, so we need to poke them to perform
+ // cleanup right now so the appropriate events fire.
+ transceiver->Shutdown_m();
+ }
+
+ mTransceivers.clear();
+
+ mQueuedIceCtxOperations.clear();
+
+ // Shutdown the transport (async)
+ RUN_ON_THREAD(
+ mSTSThread,
+ WrapRunnable(this, &PeerConnectionMedia::ShutdownMediaTransport_s),
+ NS_DISPATCH_NORMAL);
+ mParent = nullptr;
+
+ CSFLogDebug(LOGTAG, "%s: Media shut down", __FUNCTION__);
+}
+
+void PeerConnectionMedia::SelfDestruct_m() {
+ CSFLogDebug(LOGTAG, "%s: ", __FUNCTION__);
+
+ ASSERT_ON_THREAD(mMainThread);
+
+ mTransportHandler->RemoveTransportsExcept(std::set<std::string>());
+ mTransportHandler = nullptr;
+
+ mMainThread = nullptr;
+
+ // Final self-destruct.
+ this->Release();
+}
+
+void PeerConnectionMedia::ShutdownMediaTransport_s() {
+ ASSERT_ON_THREAD(mSTSThread);
+
+ CSFLogDebug(LOGTAG, "%s: ", __FUNCTION__);
+
+ disconnect_all();
+
+ // we're holding a ref to 'this' that's released by SelfDestruct_m
+ mMainThread->Dispatch(
+ WrapRunnable(this, &PeerConnectionMedia::SelfDestruct_m),
+ NS_DISPATCH_NORMAL);
+}
+
+nsresult PeerConnectionMedia::AddTransceiver(
+ JsepTransceiver* aJsepTransceiver, dom::MediaStreamTrack* aSendTrack,
+ RefPtr<TransceiverImpl>* aTransceiverImpl) {
+ if (!mCall) {
+ mCall = WebRtcCallWrapper::Create(mParent->GetTimestampMaker());
+ }
+
+ RefPtr<TransceiverImpl> transceiver = new TransceiverImpl(
+ mParent->GetWindow(), mParent->PrivacyNeeded(), mParent->GetHandle(),
+ mTransportHandler, aJsepTransceiver, mMainThread.get(), mSTSThread.get(),
+ aSendTrack, mCall.get());
+
+ if (!transceiver->IsValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aSendTrack) {
+ // implement checking for peerIdentity (where failure == black/silence)
+ Document* doc = mParent->GetWindow()->GetExtantDoc();
+ if (doc) {
+ transceiver->UpdateSinkIdentity(nullptr, doc->NodePrincipal(),
+ mParent->GetPeerIdentity());
+ } else {
+ MOZ_CRASH();
+ return NS_ERROR_FAILURE; // Don't remove this till we know it's safe.
+ }
+ }
+
+ mTransceivers.push_back(transceiver);
+ *aTransceiverImpl = transceiver;
+
+ return NS_OK;
+}
+
+void PeerConnectionMedia::GetTransmitPipelinesMatching(
+ const MediaStreamTrack* aTrack,
+ nsTArray<RefPtr<MediaPipelineTransmit>>* aPipelines) {
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (transceiver->HasSendTrack(aTrack)) {
+ aPipelines->AppendElement(transceiver->GetSendPipeline());
+ }
+ }
+}
+
+std::string PeerConnectionMedia::GetTransportIdMatchingSendTrack(
+ const dom::MediaStreamTrack& aTrack) const {
+ for (const RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (transceiver->HasSendTrack(&aTrack)) {
+ return transceiver->GetTransportId();
+ }
+ }
+ return std::string();
+}
+
+void PeerConnectionMedia::IceGatheringStateChange_s(
+ dom::RTCIceGatheringState aState) {
+ ASSERT_ON_THREAD(mSTSThread);
+
+ // ShutdownMediaTransport_s has not run yet because it unhooks this function
+ // from its signal, which means that SelfDestruct_m has not been dispatched
+ // yet either, so this PCMedia will still be around when this dispatch reaches
+ // main.
+ GetMainThread()->Dispatch(
+ WrapRunnable(this, &PeerConnectionMedia::IceGatheringStateChange_m,
+ aState),
+ NS_DISPATCH_NORMAL);
+}
+
+void PeerConnectionMedia::IceConnectionStateChange_s(
+ dom::RTCIceConnectionState aState) {
+ ASSERT_ON_THREAD(mSTSThread);
+ // ShutdownMediaTransport_s has not run yet because it unhooks this function
+ // from its signal, which means that SelfDestruct_m has not been dispatched
+ // yet either, so this PCMedia will still be around when this dispatch reaches
+ // main.
+ GetMainThread()->Dispatch(
+ WrapRunnable(this, &PeerConnectionMedia::IceConnectionStateChange_m,
+ aState),
+ NS_DISPATCH_NORMAL);
+}
+
+void PeerConnectionMedia::OnCandidateFound_s(
+ const std::string& aTransportId, const CandidateInfo& aCandidateInfo) {
+ ASSERT_ON_THREAD(mSTSThread);
+ MOZ_RELEASE_ASSERT(mTransportHandler);
+
+ CSFLogDebug(LOGTAG, "%s: %s", __FUNCTION__, aTransportId.c_str());
+
+ MOZ_ASSERT(!aCandidateInfo.mUfrag.empty());
+
+ // ShutdownMediaTransport_s has not run yet because it unhooks this function
+ // from its signal, which means that SelfDestruct_m has not been dispatched
+ // yet either, so this PCMedia will still be around when this dispatch reaches
+ // main.
+ GetMainThread()->Dispatch(
+ WrapRunnable(this, &PeerConnectionMedia::OnCandidateFound_m, aTransportId,
+ aCandidateInfo),
+ NS_DISPATCH_NORMAL);
+}
+
+void PeerConnectionMedia::IceGatheringStateChange_m(
+ dom::RTCIceGatheringState aState) {
+ ASSERT_ON_THREAD(mMainThread);
+ if (mParent) {
+ mParent->IceGatheringStateChange(aState);
+ }
+}
+
+void PeerConnectionMedia::IceConnectionStateChange_m(
+ dom::RTCIceConnectionState aState) {
+ ASSERT_ON_THREAD(mMainThread);
+ if (mParent) {
+ mParent->IceConnectionStateChange(aState);
+ }
+}
+
+void PeerConnectionMedia::OnCandidateFound_m(
+ const std::string& aTransportId, const CandidateInfo& aCandidateInfo) {
+ ASSERT_ON_THREAD(mMainThread);
+ if (mStunAddrsRequest && !aCandidateInfo.mMDNSAddress.empty()) {
+ MOZ_ASSERT(!aCandidateInfo.mActualAddress.empty());
+
+ if (mCanRegisterMDNSHostnamesDirectly) {
+ auto itor = mRegisteredMDNSHostnames.find(aCandidateInfo.mMDNSAddress);
+
+ // We'll see the address twice if we're generating both UDP and TCP
+ // candidates.
+ if (itor == mRegisteredMDNSHostnames.end()) {
+ mRegisteredMDNSHostnames.insert(aCandidateInfo.mMDNSAddress);
+ mStunAddrsRequest->SendRegisterMDNSHostname(
+ nsCString(aCandidateInfo.mMDNSAddress.c_str()),
+ nsCString(aCandidateInfo.mActualAddress.c_str()));
+ }
+ } else {
+ mMDNSHostnamesToRegister.emplace(aCandidateInfo.mMDNSAddress,
+ aCandidateInfo.mActualAddress);
+ }
+ }
+
+ if (mParent) {
+ mParent->OnCandidateFound(aTransportId, aCandidateInfo);
+ }
+}
+
+void PeerConnectionMedia::AlpnNegotiated_s(const std::string& aAlpn,
+ bool aPrivacyRequested) {
+ MOZ_DIAGNOSTIC_ASSERT((aAlpn == "c-webrtc") == aPrivacyRequested);
+ GetMainThread()->Dispatch(
+ WrapRunnable(this, &PeerConnectionMedia::AlpnNegotiated_m,
+ aPrivacyRequested),
+ NS_DISPATCH_NORMAL);
+}
+
+void PeerConnectionMedia::AlpnNegotiated_m(bool aPrivacyRequested) {
+ if (mParent) {
+ mParent->OnAlpnNegotiated(aPrivacyRequested);
+ }
+}
+
+/**
+ * Tells you if any local track is isolated to a specific peer identity.
+ * Obviously, we want all the tracks to be isolated equally so that they can
+ * all be sent or not. We check once when we are setting a local description
+ * and that determines if we flip the "privacy requested" bit on. Once the bit
+ * is on, all media originating from this peer connection is isolated.
+ *
+ * @returns true if any track has a peerIdentity set on it
+ */
+bool PeerConnectionMedia::AnyLocalTrackHasPeerIdentity() const {
+ ASSERT_ON_THREAD(mMainThread);
+
+ for (const RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (transceiver->GetSendTrack() &&
+ transceiver->GetSendTrack()->GetPeerIdentity()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void PeerConnectionMedia::UpdateSinkIdentity_m(
+ const MediaStreamTrack* aTrack, nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity) {
+ ASSERT_ON_THREAD(mMainThread);
+
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ transceiver->UpdateSinkIdentity(aTrack, aPrincipal, aSinkIdentity);
+ }
+}
+
+bool PeerConnectionMedia::AnyCodecHasPluginID(uint64_t aPluginID) {
+ for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) {
+ if (transceiver->ConduitHasPluginID(aPluginID)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsPIDOMWindowInner* PeerConnectionMedia::GetWindow() const {
+ return mParent->GetWindow();
+}
+
+std::unique_ptr<NrSocketProxyConfig> PeerConnectionMedia::GetProxyConfig()
+ const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mForceProxy &&
+ Preferences::GetBool("media.peerconnection.disable_http_proxy", false)) {
+ return nullptr;
+ }
+
+ nsCString alpn = "webrtc,c-webrtc"_ns;
+ auto browserChild = BrowserChild::GetFrom(GetWindow());
+ if (!browserChild) {
+ // Android doesn't have browser child apparently...
+ return nullptr;
+ }
+
+ Document* doc = mParent->GetWindow()->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ NS_WARNING("Unable to get document from window");
+ return nullptr;
+ }
+
+ TabId id = browserChild->GetTabId();
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new net::LoadInfo(doc->NodePrincipal(), doc->NodePrincipal(), doc, 0,
+ nsIContentPolicy::TYPE_INVALID);
+
+ Maybe<net::LoadInfoArgs> loadInfoArgs;
+ MOZ_ALWAYS_SUCCEEDS(
+ mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
+ return std::unique_ptr<NrSocketProxyConfig>(new NrSocketProxyConfig(
+ net::WebrtcProxyConfig(id, alpn, *loadInfoArgs, mForceProxy)));
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/PeerConnectionMedia.h b/dom/media/webrtc/jsapi/PeerConnectionMedia.h
new file mode 100644
index 0000000000..bc89029545
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PeerConnectionMedia.h
@@ -0,0 +1,262 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _PEER_CONNECTION_MEDIA_H_
+#define _PEER_CONNECTION_MEDIA_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/StunAddrsRequestChild.h"
+#include "MediaTransportHandler.h"
+#include "nsIHttpChannelInternal.h"
+#include "RTCDtlsTransport.h"
+
+#include "TransceiverImpl.h"
+
+class nsIPrincipal;
+
+namespace mozilla {
+class DataChannel;
+class PeerIdentity;
+namespace dom {
+class MediaStreamTrack;
+} // namespace dom
+} // namespace mozilla
+
+#include "transport/nriceresolver.h"
+#include "transport/nricemediastream.h"
+
+namespace mozilla {
+
+class PeerConnectionImpl;
+class PeerConnectionMedia;
+class PCUuidGenerator;
+class MediaPipeline;
+class MediaPipelineReceive;
+class MediaPipelineTransmit;
+class MediaPipelineFilter;
+class JsepSession;
+
+// TODO(bug 1402997): If we move the TransceiverImpl stuff out of here, this
+// will be a class that handles just the transport stuff, and we can rename it
+// to something more explanatory (say, PeerConnectionTransportManager).
+class PeerConnectionMedia : public sigslot::has_slots<> {
+ ~PeerConnectionMedia();
+
+ public:
+ explicit PeerConnectionMedia(PeerConnectionImpl* parent);
+
+ nsresult Init();
+ // WARNING: This destroys the object!
+ void SelfDestruct();
+
+ // Ensure ICE transports exist that we might need when offer/answer concludes
+ void EnsureTransports(const JsepSession& aSession);
+
+ void UpdateRTCDtlsTransports(bool aMarkAsStable);
+ void RollbackRTCDtlsTransports();
+ void RemoveRTCDtlsTransportsExcept(
+ const std::set<std::string>& aTransportIds);
+
+ // Activate ICE transports at the conclusion of offer/answer,
+ // or when rollback occurs.
+ nsresult UpdateTransports(const JsepSession& aSession,
+ const bool forceIceTcp);
+
+ void ResetStunAddrsForIceRestart() { mStunAddrs.Clear(); }
+
+ // Start ICE checks.
+ void StartIceChecks(const JsepSession& session);
+
+ // Process a trickle ICE candidate.
+ void AddIceCandidate(const std::string& candidate,
+ const std::string& aTransportId,
+ const std::string& aUFrag);
+
+ // Handle notifications of network online/offline events.
+ void UpdateNetworkState(bool online);
+
+ // Handle complete media pipelines.
+ // This updates codec parameters, starts/stops send/receive, and other
+ // stuff that doesn't necessarily require negotiation. This can be called at
+ // any time, not just when an offer/answer exchange completes.
+ // TODO: Let's move this to PeerConnectionImpl
+ nsresult UpdateMediaPipelines();
+
+ // TODO: Let's move the TransceiverImpl stuff to PeerConnectionImpl.
+ nsresult AddTransceiver(JsepTransceiver* aJsepTransceiver,
+ dom::MediaStreamTrack* aSendTrack,
+ RefPtr<TransceiverImpl>* aTransceiverImpl);
+
+ void GetTransmitPipelinesMatching(
+ const dom::MediaStreamTrack* aTrack,
+ nsTArray<RefPtr<MediaPipelineTransmit>>* aPipelines);
+
+ std::string GetTransportIdMatchingSendTrack(
+ const dom::MediaStreamTrack& aTrack) const;
+
+ // 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.
+ // aTrack will be set if this update came from a principal change on aTrack.
+ // TODO: Move to PeerConnectionImpl
+ void UpdateSinkIdentity_m(const dom::MediaStreamTrack* aTrack,
+ nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity);
+ // this determines if any track is peerIdentity constrained
+ bool AnyLocalTrackHasPeerIdentity() const;
+
+ bool AnyCodecHasPluginID(uint64_t aPluginID);
+
+ const nsCOMPtr<nsIThread>& GetMainThread() const { return mMainThread; }
+ const nsCOMPtr<nsISerialEventTarget>& GetSTSThread() const {
+ return mSTSThread;
+ }
+
+ // Used by PCImpl in a couple of places. Might be good to move that code in
+ // here.
+ std::vector<RefPtr<TransceiverImpl>>& GetTransceivers() {
+ return mTransceivers;
+ }
+
+ nsPIDOMWindowInner* GetWindow() const;
+ already_AddRefed<nsIHttpChannelInternal> GetChannel() const;
+
+ void AlpnNegotiated_s(const std::string& aAlpn, bool aPrivacyRequested);
+ void AlpnNegotiated_m(bool aPrivacyRequested);
+
+ // TODO: Move to PeerConnectionImpl
+ RefPtr<WebRtcCallWrapper> mCall;
+
+ // mtransport objects
+ RefPtr<MediaTransportHandler> mTransportHandler;
+
+ private:
+ void InitLocalAddrs(); // for stun local address IPC request
+ bool ShouldForceProxy() const;
+ std::unique_ptr<NrSocketProxyConfig> GetProxyConfig() const;
+
+ class StunAddrsHandler : public net::StunAddrsListener {
+ public:
+ explicit StunAddrsHandler(PeerConnectionMedia* pcm) : pcm_(pcm) {}
+
+ void OnMDNSQueryComplete(const nsCString& hostname,
+ const Maybe<nsCString>& address) override;
+
+ void OnStunAddrsAvailable(
+ const mozilla::net::NrIceStunAddrArray& addrs) override;
+
+ private:
+ RefPtr<PeerConnectionMedia> pcm_;
+ virtual ~StunAddrsHandler() {}
+ };
+
+ // Shutdown media transport. Must be called on STS thread.
+ void ShutdownMediaTransport_s();
+
+ // Final destruction of the media stream. Must be called on the main
+ // thread.
+ void SelfDestruct_m();
+
+ // Manage ICE transports.
+ void UpdateTransport(const JsepTransceiver& aTransceiver, bool aForceIceTcp);
+
+ void GatherIfReady();
+ void FlushIceCtxOperationQueueIfReady();
+ void PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable);
+ nsresult SetTargetForDefaultLocalAddressLookup();
+ void EnsureIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses);
+
+ bool GetPrefDefaultAddressOnly() const;
+ bool GetPrefObfuscateHostAddresses() const;
+
+ void ConnectSignals();
+
+ // ICE events
+ void IceGatheringStateChange_s(dom::RTCIceGatheringState aState);
+ void IceConnectionStateChange_s(dom::RTCIceConnectionState aState);
+ void OnCandidateFound_s(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo);
+
+ void IceGatheringStateChange_m(dom::RTCIceGatheringState aState);
+ void IceConnectionStateChange_m(dom::RTCIceConnectionState aState);
+ void OnCandidateFound_m(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo);
+
+ bool IsIceCtxReady() const {
+ return mLocalAddrsRequestState == STUN_ADDR_REQUEST_COMPLETE;
+ }
+
+ // The parent PC
+ PeerConnectionImpl* mParent;
+ // and a loose handle on it for event driven stuff
+ std::string mParentHandle;
+ std::string mParentName;
+
+ std::vector<RefPtr<TransceiverImpl>> mTransceivers;
+ std::map<std::string, RefPtr<dom::RTCDtlsTransport>>
+ mTransportIdToRTCDtlsTransport;
+
+ // The main thread.
+ nsCOMPtr<nsIThread> mMainThread;
+
+ // The STS thread.
+ nsCOMPtr<nsISerialEventTarget> mSTSThread;
+
+ // Used whenever we need to dispatch a runnable to STS to tweak something
+ // on our ICE ctx, but are not ready to do so at the moment (eg; we are
+ // waiting to get a callback with our http proxy config before we start
+ // gathering or start checking)
+ std::vector<nsCOMPtr<nsIRunnable>> mQueuedIceCtxOperations;
+
+ // Set if prefs dictate that we should force the use of a web proxy.
+ bool mForceProxy;
+
+ // Used to cancel incoming stun addrs response
+ RefPtr<net::StunAddrsRequestChild> mStunAddrsRequest;
+
+ enum StunAddrRequestState {
+ STUN_ADDR_REQUEST_NONE,
+ STUN_ADDR_REQUEST_PENDING,
+ STUN_ADDR_REQUEST_COMPLETE
+ };
+ // Used to track the state of the stun addr IPC request
+ StunAddrRequestState mLocalAddrsRequestState;
+
+ // Used to store the result of the stun addr IPC request
+ nsTArray<NrIceStunAddr> mStunAddrs;
+
+ // Used to ensure the target for default local address lookup is only set
+ // once.
+ bool mTargetForDefaultLocalAddressLookupIsSet;
+
+ // Set to true when the object is going to be released.
+ bool mDestroyed;
+
+ // Keep track of local hostnames to register. Registration is deferred
+ // until StartIceChecks has run. Accessed on main thread only.
+ std::map<std::string, std::string> mMDNSHostnamesToRegister;
+ bool mCanRegisterMDNSHostnamesDirectly = false;
+
+ // Used to store the mDNS hostnames that we have registered
+ std::set<std::string> mRegisteredMDNSHostnames;
+
+ // Used to store the mDNS hostnames that we have queried
+ struct PendingIceCandidate {
+ std::vector<std::string> mTokenizedCandidate;
+ std::string mTransportId;
+ std::string mUfrag;
+ };
+ std::map<std::string, std::list<PendingIceCandidate>> mQueriedMDNSHostnames;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PeerConnectionMedia)
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsapi/RTCDTMFSender.cpp b/dom/media/webrtc/jsapi/RTCDTMFSender.cpp
new file mode 100644
index 0000000000..0939399c55
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCDTMFSender.cpp
@@ -0,0 +1,150 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "RTCDTMFSender.h"
+#include "libwebrtcglue/MediaConduitInterface.h"
+#include "transport/logging.h"
+#include "TransceiverImpl.h"
+#include "nsITimer.h"
+#include "mozilla/dom/RTCDTMFSenderBinding.h"
+#include "mozilla/dom/RTCDTMFToneChangeEvent.h"
+#include <algorithm>
+#include <bitset>
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDTMFSender, DOMEventTargetHelper,
+ mTransceiver, mSendTimer)
+
+NS_IMPL_ADDREF_INHERITED(RTCDTMFSender, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(RTCDTMFSender, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCDTMFSender)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+LazyLogModule gDtmfLog("RTCDTMFSender");
+
+RTCDTMFSender::RTCDTMFSender(nsPIDOMWindowInner* aWindow,
+ TransceiverImpl* aTransceiver,
+ AudioSessionConduit* aConduit)
+ : DOMEventTargetHelper(aWindow),
+ mTransceiver(aTransceiver),
+ mConduit(aConduit) {}
+
+JSObject* RTCDTMFSender::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCDTMFSender_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+static int GetDTMFToneCode(uint16_t c) {
+ const char* DTMF_TONECODES = "0123456789*#ABCD";
+
+ if (c == ',') {
+ // , is a special character indicating a 2 second delay
+ return -1;
+ }
+
+ const char* i = strchr(DTMF_TONECODES, c);
+ MOZ_ASSERT(i);
+ return static_cast<int>(i - DTMF_TONECODES);
+}
+
+static std::bitset<256> GetCharacterBitset(const std::string& aCharsInSet) {
+ std::bitset<256> result;
+ for (auto c : aCharsInSet) {
+ result[c] = true;
+ }
+ return result;
+}
+
+static bool IsUnrecognizedChar(const char c) {
+ static const std::bitset<256> recognized =
+ GetCharacterBitset("0123456789ABCD#*,");
+ return !recognized[c];
+}
+
+void RTCDTMFSender::InsertDTMF(const nsAString& aTones, uint32_t aDuration,
+ uint32_t aInterToneGap, ErrorResult& aRv) {
+ if (!mTransceiver->CanSendDTMF()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ std::string utf8Tones = NS_ConvertUTF16toUTF8(aTones).get();
+
+ std::transform(utf8Tones.begin(), utf8Tones.end(), utf8Tones.begin(),
+ [](const char c) { return std::toupper(c); });
+
+ if (std::any_of(utf8Tones.begin(), utf8Tones.end(), IsUnrecognizedChar)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+ return;
+ }
+
+ CopyUTF8toUTF16(utf8Tones, mToneBuffer);
+ mDuration = std::clamp(aDuration, 40U, 6000U);
+ mInterToneGap = std::clamp(aInterToneGap, 30U, 6000U);
+
+ if (mToneBuffer.Length()) {
+ StartPlayout(0);
+ }
+}
+
+void RTCDTMFSender::StopPlayout() {
+ if (mSendTimer) {
+ mSendTimer->Cancel();
+ mSendTimer = nullptr;
+ }
+}
+
+void RTCDTMFSender::StartPlayout(uint32_t aDelay) {
+ if (!mSendTimer) {
+ mSendTimer = NS_NewTimer();
+ mSendTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+nsresult RTCDTMFSender::Notify(nsITimer* timer) {
+ MOZ_ASSERT(NS_IsMainThread());
+ StopPlayout();
+
+ if (!mTransceiver->IsSending()) {
+ return NS_OK;
+ }
+
+ RTCDTMFToneChangeEventInit init;
+ if (!mToneBuffer.IsEmpty()) {
+ uint16_t toneChar = mToneBuffer.CharAt(0);
+ int tone = GetDTMFToneCode(toneChar);
+
+ init.mTone.Assign(toneChar);
+
+ mToneBuffer.Cut(0, 1);
+
+ if (tone == -1) {
+ StartPlayout(2000);
+ } else {
+ // Reset delay if necessary
+ StartPlayout(mDuration + mInterToneGap);
+ // Note: We default to channel 0, not inband, and 6dB attenuation.
+ // here. We might want to revisit these choices in the future.
+ mConduit->InsertDTMFTone(0, tone, true, mDuration, 6);
+ }
+ }
+
+ RefPtr<RTCDTMFToneChangeEvent> event =
+ RTCDTMFToneChangeEvent::Constructor(this, u"tonechange"_ns, init);
+ DispatchTrustedEvent(event);
+
+ return NS_OK;
+}
+
+void RTCDTMFSender::GetToneBuffer(nsAString& aOutToneBuffer) {
+ aOutToneBuffer = mToneBuffer;
+}
+
+} // namespace mozilla::dom
+
+#undef LOGTAG
diff --git a/dom/media/webrtc/jsapi/RTCDTMFSender.h b/dom/media/webrtc/jsapi/RTCDTMFSender.h
new file mode 100644
index 0000000000..8ca07debbe
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCDTMFSender.h
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _RTCDTMFSender_h_
+#define _RTCDTMFSender_h_
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/RefPtr.h"
+#include "js/RootingAPI.h"
+#include "nsITimer.h"
+
+class nsPIDOMWindowInner;
+class nsITimer;
+
+namespace mozilla {
+class AudioSessionConduit;
+class TransceiverImpl;
+
+namespace dom {
+
+class RTCDTMFSender : public DOMEventTargetHelper, public nsITimerCallback {
+ public:
+ explicit RTCDTMFSender(nsPIDOMWindowInner* aWindow,
+ TransceiverImpl* aTransceiver,
+ AudioSessionConduit* aConduit);
+
+ // nsISupports
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCDTMFSender, DOMEventTargetHelper)
+
+ // webidl
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ void InsertDTMF(const nsAString& aTones, uint32_t aDuration,
+ uint32_t aInterToneGap, ErrorResult& aRv);
+ void GetToneBuffer(nsAString& aOutToneBuffer);
+ IMPL_EVENT_HANDLER(tonechange)
+
+ void StopPlayout();
+
+ private:
+ virtual ~RTCDTMFSender() = default;
+
+ void StartPlayout(uint32_t aDelay);
+
+ RefPtr<TransceiverImpl> mTransceiver;
+ RefPtr<AudioSessionConduit> mConduit;
+ nsString mToneBuffer;
+ uint32_t mDuration = 0;
+ uint32_t mInterToneGap = 0;
+ nsCOMPtr<nsITimer> mSendTimer;
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif // _RTCDTMFSender_h_
diff --git a/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp b/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp
new file mode 100644
index 0000000000..442e9c7a17
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "RTCDtlsTransport.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventBinding.h"
+#include "mozilla/dom/RTCDtlsTransportBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDtlsTransport, DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(RTCDtlsTransport, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(RTCDtlsTransport, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCDtlsTransport)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+RTCDtlsTransport::RTCDtlsTransport(nsPIDOMWindowInner* aWindow)
+ : DOMEventTargetHelper(aWindow), mState(RTCDtlsTransportState::New) {}
+
+JSObject* RTCDtlsTransport::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCDtlsTransport_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void RTCDtlsTransport::UpdateState(TransportLayer::State aState) {
+ if (mState == RTCDtlsTransportState::Closed) {
+ return;
+ }
+
+ RTCDtlsTransportState newState = mState;
+ switch (aState) {
+ case TransportLayer::TS_NONE:
+ break;
+ case TransportLayer::TS_INIT:
+ break;
+ case TransportLayer::TS_CONNECTING:
+ newState = RTCDtlsTransportState::Connecting;
+ break;
+ case TransportLayer::TS_OPEN:
+ newState = RTCDtlsTransportState::Connected;
+ break;
+ case TransportLayer::TS_CLOSED:
+ newState = RTCDtlsTransportState::Closed;
+ break;
+ case TransportLayer::TS_ERROR:
+ newState = RTCDtlsTransportState::Failed;
+ break;
+ }
+
+ if (newState == mState) {
+ return;
+ }
+
+ mState = newState;
+
+ EventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ RefPtr<Event> event = Event::Constructor(this, u"statechange"_ns, init);
+
+ DispatchTrustedEvent(event);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/RTCDtlsTransport.h b/dom/media/webrtc/jsapi/RTCDtlsTransport.h
new file mode 100644
index 0000000000..4318e20c61
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.h
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _RTCDtlsTransport_h_
+#define _RTCDtlsTransport_h_
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/RefPtr.h"
+#include "js/RootingAPI.h"
+#include "transport/transportlayer.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+enum class RTCDtlsTransportState : uint8_t;
+
+class RTCDtlsTransport : public DOMEventTargetHelper {
+ public:
+ explicit RTCDtlsTransport(nsPIDOMWindowInner* aWindow);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCDtlsTransport,
+ DOMEventTargetHelper)
+
+ // webidl
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ IMPL_EVENT_HANDLER(statechange)
+ RTCDtlsTransportState State() const { return mState; }
+
+ void UpdateState(TransportLayer::State aState);
+
+ private:
+ virtual ~RTCDtlsTransport() = default;
+
+ RTCDtlsTransportState mState;
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif // _RTCDtlsTransport_h_
diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp
new file mode 100644
index 0000000000..972b81adba
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp
@@ -0,0 +1,619 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "RTCRtpReceiver.h"
+#include "transport/logging.h"
+#include "mozilla/dom/MediaStreamTrack.h"
+#include "mozilla/dom/Promise.h"
+#include "transportbridge/MediaPipeline.h"
+#include "nsPIDOMWindow.h"
+#include "PrincipalHandle.h"
+#include "nsIPrincipal.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/NullPrincipal.h"
+#include "MediaTrackGraph.h"
+#include "RemoteTrackSource.h"
+#include "libwebrtcglue/RtpRtcpConfig.h"
+#include "nsString.h"
+#include "mozilla/dom/AudioStreamTrack.h"
+#include "mozilla/dom/VideoStreamTrack.h"
+#include "MediaTransportHandler.h"
+#include "jsep/JsepTransceiver.h"
+#include "mozilla/dom/RTCRtpReceiverBinding.h"
+#include "mozilla/dom/RTCRtpSourcesBinding.h"
+#include "RTCStatsReport.h"
+#include "mozilla/Preferences.h"
+#include "TransceiverImpl.h"
+#include "libwebrtcglue/AudioConduit.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCRtpReceiver, mWindow, mTrack)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpReceiver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpReceiver)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpReceiver)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+LazyLogModule gReceiverLog("RTCRtpReceiver");
+
+static PrincipalHandle GetPrincipalHandle(nsPIDOMWindowInner* aWindow,
+ bool aPrivacyNeeded) {
+ // Set the principal used for creating the tracks. This makes the track
+ // data (audio/video samples) accessible to the receiving page. We're
+ // only certain that privacy hasn't been requested if we're connected.
+ nsCOMPtr<nsIScriptObjectPrincipal> winPrincipal = do_QueryInterface(aWindow);
+ RefPtr<nsIPrincipal> principal = winPrincipal->GetPrincipal();
+ if (NS_WARN_IF(!principal)) {
+ principal = NullPrincipal::CreateWithoutOriginAttributes();
+ } else if (aPrivacyNeeded) {
+ principal = NullPrincipal::CreateWithInheritedAttributes(principal);
+ }
+ return MakePrincipalHandle(principal);
+}
+
+static already_AddRefed<dom::MediaStreamTrack> CreateTrack(
+ nsPIDOMWindowInner* aWindow, bool aAudio,
+ const nsCOMPtr<nsIPrincipal>& aPrincipal) {
+ MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
+ aAudio ? MediaTrackGraph::AUDIO_THREAD_DRIVER
+ : MediaTrackGraph::SYSTEM_THREAD_DRIVER,
+ aWindow, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
+ MediaTrackGraph::DEFAULT_OUTPUT_DEVICE);
+
+ RefPtr<MediaStreamTrack> track;
+ RefPtr<RemoteTrackSource> trackSource;
+ if (aAudio) {
+ RefPtr<SourceMediaTrack> source =
+ graph->CreateSourceTrack(MediaSegment::AUDIO);
+ trackSource = new RemoteTrackSource(source, aPrincipal, u"remote audio"_ns);
+ track = new AudioStreamTrack(aWindow, source, trackSource);
+ } else {
+ RefPtr<SourceMediaTrack> source =
+ graph->CreateSourceTrack(MediaSegment::VIDEO);
+ trackSource = new RemoteTrackSource(source, aPrincipal, u"remote video"_ns);
+ track = new VideoStreamTrack(aWindow, source, trackSource);
+ }
+
+ // Spec says remote tracks start out muted.
+ trackSource->SetMuted(true);
+
+ return track.forget();
+}
+
+RTCRtpReceiver::RTCRtpReceiver(nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded,
+ const std::string& aPCHandle,
+ MediaTransportHandler* aTransportHandler,
+ JsepTransceiver* aJsepTransceiver,
+ nsISerialEventTarget* aMainThread,
+ nsISerialEventTarget* aStsThread,
+ MediaSessionConduit* aConduit,
+ TransceiverImpl* aTransceiverImpl)
+ : mWindow(aWindow),
+ mPCHandle(aPCHandle),
+ mJsepTransceiver(aJsepTransceiver),
+ mMainThread(aMainThread),
+ mStsThread(aStsThread),
+ mTransportHandler(aTransportHandler),
+ mTransceiverImpl(aTransceiverImpl) {
+ PrincipalHandle principalHandle = GetPrincipalHandle(aWindow, aPrivacyNeeded);
+ mTrack = CreateTrack(aWindow, aConduit->type() == MediaSessionConduit::AUDIO,
+ principalHandle.get());
+ // Until Bug 1232234 is fixed, we'll get extra RTCP BYES during renegotiation,
+ // so we'll disable muting on RTCP BYE and timeout for now.
+ if (Preferences::GetBool("media.peerconnection.mute_on_bye_or_timeout",
+ false)) {
+ aConduit->SetRtcpEventObserver(this);
+ }
+ if (aConduit->type() == MediaSessionConduit::AUDIO) {
+ mPipeline = new MediaPipelineReceiveAudio(
+ mPCHandle, aTransportHandler, mMainThread.get(), mStsThread.get(),
+ static_cast<AudioSessionConduit*>(aConduit), mTrack, principalHandle);
+ } else {
+ mPipeline = new MediaPipelineReceiveVideo(
+ mPCHandle, aTransportHandler, mMainThread.get(), mStsThread.get(),
+ static_cast<VideoSessionConduit*>(aConduit), mTrack, principalHandle);
+ }
+}
+
+RTCRtpReceiver::~RTCRtpReceiver() { MOZ_ASSERT(!mPipeline); }
+
+JSObject* RTCRtpReceiver::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCRtpReceiver_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+RTCDtlsTransport* RTCRtpReceiver::GetTransport() const {
+ if (!mTransceiverImpl) {
+ return nullptr;
+ }
+ return mTransceiverImpl->GetDtlsTransport();
+}
+
+already_AddRefed<Promise> RTCRtpReceiver::GetStats() {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(global, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.StealNSResult();
+ return nullptr;
+ }
+
+ // Two promises; one for RTP/RTCP stats, another for ICE stats.
+ auto promises = GetStatsInternal();
+ RTCStatsPromise::All(mMainThread, promises)
+ ->Then(
+ mMainThread, __func__,
+ [promise, window = mWindow](
+ const nsTArray<UniquePtr<RTCStatsCollection>>& aStats) {
+ RefPtr<RTCStatsReport> report(new RTCStatsReport(window));
+ for (const auto& stats : aStats) {
+ report->Incorporate(*stats);
+ }
+ promise->MaybeResolve(std::move(report));
+ },
+ [promise](nsresult aError) {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ });
+
+ return promise.forget();
+}
+
+static UniquePtr<dom::RTCStatsCollection> GetReceiverStats_s(
+ const RefPtr<MediaPipelineReceive>& aPipeline,
+ const nsString& aRecvTrackId) {
+ UniquePtr<dom::RTCStatsCollection> report(new dom::RTCStatsCollection);
+ auto asVideo = aPipeline->Conduit()->AsVideoSessionConduit();
+
+ nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns;
+ nsString idstr = kind + u"_"_ns;
+ idstr.AppendInt(static_cast<uint32_t>(aPipeline->Level()));
+
+ // Add bandwidth estimation stats
+ aPipeline->Conduit()->GetBandwidthEstimation().apply([&](auto& bw) {
+ bw.mTrackIdentifier = aRecvTrackId;
+ if (!report->mBandwidthEstimations.AppendElement(bw, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+
+ Maybe<uint32_t> ssrc;
+ unsigned int ssrcval;
+ if (aPipeline->Conduit()->GetRemoteSSRC(&ssrcval)) {
+ ssrc = Some(ssrcval);
+ }
+
+ // Add frame history
+ asVideo.apply([&](const auto& conduit) {
+ if (conduit->AddFrameHistory(&report->mVideoFrameHistories)) {
+ auto& history = report->mVideoFrameHistories.LastElement();
+ history.mTrackIdentifier = aRecvTrackId;
+ }
+ });
+
+ // TODO(@@NG):ssrcs handle Conduits having multiple stats at the same level
+ // This is pending spec work
+ // Gather pipeline stats.
+ nsString localId = u"inbound_rtp_"_ns + idstr;
+ nsString remoteId;
+
+ // First, fill in remote stat with rtcp sender data, if present.
+ uint32_t packetsSent;
+ uint64_t bytesSent;
+ DOMHighResTimeStamp remoteTimestamp;
+ Maybe<DOMHighResTimeStamp> timestamp =
+ aPipeline->Conduit()->LastRtcpReceived();
+ if (timestamp.isSome() && aPipeline->Conduit()->GetRTCPSenderReport(
+ &packetsSent, &bytesSent, &remoteTimestamp)) {
+ RTCRemoteOutboundRtpStreamStats s;
+ remoteId = u"inbound_rtcp_"_ns + idstr;
+ s.mTimestamp.Construct(*timestamp);
+ s.mId.Construct(remoteId);
+ s.mType.Construct(RTCStatsType::Remote_outbound_rtp);
+ ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); });
+ s.mMediaType.Construct(kind); // mediaType is the old name for kind.
+ s.mKind.Construct(kind);
+ s.mLocalId.Construct(localId);
+ s.mPacketsSent.Construct(packetsSent);
+ s.mBytesSent.Construct(bytesSent);
+ s.mRemoteTimestamp.Construct(remoteTimestamp);
+ if (!report->mRemoteOutboundRtpStreamStats.AppendElement(s, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ // Then, fill in local side (with cross-link to remote only if present)
+ RTCInboundRtpStreamStats s;
+ // TODO(bug 1496533): Should we use the time of the most-recently received
+ // RTP packet? If so, what do we use if we haven't received any RTP? Now?
+ s.mTimestamp.Construct(aPipeline->GetNow());
+ s.mId.Construct(localId);
+ s.mType.Construct(RTCStatsType::Inbound_rtp);
+ ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); });
+ s.mMediaType.Construct(kind); // mediaType is the old name for kind.
+ s.mKind.Construct(kind);
+ unsigned int jitterMs, packetsLost;
+ if (aPipeline->Conduit()->GetRTPReceiverStats(&jitterMs, &packetsLost)) {
+ s.mJitter.Construct(double(jitterMs) / 1000);
+ s.mPacketsLost.Construct(packetsLost);
+ }
+ if (remoteId.Length()) {
+ s.mRemoteId.Construct(remoteId);
+ }
+ s.mPacketsReceived.Construct(aPipeline->RtpPacketsReceived());
+ s.mBytesReceived.Construct(aPipeline->RtpBytesReceived());
+
+ // Fill in packet type statistics
+ webrtc::RtcpPacketTypeCounter counters;
+ if (aPipeline->Conduit()->GetRecvPacketTypeStats(&counters)) {
+ s.mNackCount.Construct(counters.nack_packets);
+ // Fill in video only packet type stats
+ if (asVideo) {
+ s.mFirCount.Construct(counters.fir_packets);
+ s.mPliCount.Construct(counters.pli_packets);
+ }
+ }
+ // Lastly, fill in video decoder stats if this is video
+ asVideo.apply([&s](auto conduit) {
+ double framerateMean;
+ double framerateStdDev;
+ double bitrateMean;
+ double bitrateStdDev;
+ uint32_t discardedPackets;
+ uint32_t framesDecoded;
+ if (conduit->GetVideoDecoderStats(&framerateMean, &framerateStdDev,
+ &bitrateMean, &bitrateStdDev,
+ &discardedPackets, &framesDecoded)) {
+ s.mFramerateMean.Construct(framerateMean);
+ s.mFramerateStdDev.Construct(framerateStdDev);
+ s.mBitrateMean.Construct(bitrateMean);
+ s.mBitrateStdDev.Construct(bitrateStdDev);
+ s.mDiscardedPackets.Construct(discardedPackets);
+ s.mFramesDecoded.Construct(framesDecoded);
+ }
+ });
+ if (!report->mInboundRtpStreamStats.AppendElement(s, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+
+ // Fill in Contributing Source statistics
+ aPipeline->GetContributingSourceStats(localId,
+ report->mRtpContributingSourceStats);
+
+ return report;
+}
+
+nsTArray<RefPtr<RTCStatsPromise>> RTCRtpReceiver::GetStatsInternal() {
+ nsTArray<RefPtr<RTCStatsPromise>> promises;
+ if (mPipeline && mHaveStartedReceiving) {
+ nsString recvTrackId;
+ MOZ_ASSERT(mTrack);
+ if (mTrack) {
+ mTrack->GetId(recvTrackId);
+ }
+ promises.AppendElement(InvokeAsync(
+ mStsThread, __func__, [pipeline = mPipeline, recvTrackId]() {
+ return RTCStatsPromise::CreateAndResolve(
+ GetReceiverStats_s(pipeline, recvTrackId), __func__);
+ }));
+
+ if (mJsepTransceiver->mTransport.mComponents) {
+ promises.AppendElement(mTransportHandler->GetIceStats(
+ mJsepTransceiver->mTransport.mTransportId, mPipeline->GetNow()));
+ }
+ }
+ return promises;
+}
+
+void RTCRtpReceiver::GetContributingSources(
+ nsTArray<RTCRtpContributingSource>& aSources) {
+ // Duplicate code...
+ if (mPipeline && mPipeline->Conduit()) {
+ RefPtr<AudioSessionConduit> conduit(
+ static_cast<AudioSessionConduit*>(mPipeline->Conduit()));
+ nsTArray<dom::RTCRtpSourceEntry> sources;
+ conduit->GetRtpSources(sources);
+ sources.RemoveElementsBy([](const dom::RTCRtpSourceEntry& aEntry) {
+ return aEntry.mSourceType != dom::RTCRtpSourceEntryType::Contributing;
+ });
+ aSources.ReplaceElementsAt(0, aSources.Length(), sources.Elements(),
+ sources.Length());
+ }
+}
+
+void RTCRtpReceiver::GetSynchronizationSources(
+ nsTArray<dom::RTCRtpSynchronizationSource>& aSources) {
+ // Duplicate code...
+ if (mPipeline && mPipeline->Conduit()) {
+ RefPtr<AudioSessionConduit> conduit(
+ static_cast<AudioSessionConduit*>(mPipeline->Conduit()));
+ nsTArray<dom::RTCRtpSourceEntry> sources;
+ conduit->GetRtpSources(sources);
+ sources.RemoveElementsBy([](const dom::RTCRtpSourceEntry& aEntry) {
+ return aEntry.mSourceType != dom::RTCRtpSourceEntryType::Synchronization;
+ });
+ aSources.ReplaceElementsAt(0, aSources.Length(), sources.Elements(),
+ sources.Length());
+ }
+}
+
+nsPIDOMWindowInner* RTCRtpReceiver::GetParentObject() const { return mWindow; }
+
+void RTCRtpReceiver::Shutdown() {
+ ASSERT_ON_THREAD(mMainThread);
+ if (mPipeline) {
+ mPipeline->Shutdown_m();
+ mPipeline->Conduit()->SetRtcpEventObserver(nullptr);
+ mPipeline = nullptr;
+ }
+ mTransceiverImpl = nullptr;
+}
+
+void RTCRtpReceiver::UpdateTransport() {
+ ASSERT_ON_THREAD(mMainThread);
+ if (!mHaveSetupTransport) {
+ mPipeline->SetLevel(mJsepTransceiver->GetLevel());
+ mHaveSetupTransport = true;
+ }
+
+ UniquePtr<MediaPipelineFilter> filter;
+
+ auto const& details = mJsepTransceiver->mRecvTrack.GetNegotiatedDetails();
+ if (mJsepTransceiver->HasBundleLevel() && details) {
+ std::vector<webrtc::RtpExtension> extmaps;
+ details->ForEachRTPHeaderExtension(
+ [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) {
+ extmaps.emplace_back(extmap.extensionname, extmap.entry);
+ });
+ filter = MakeUnique<MediaPipelineFilter>(extmaps);
+
+ // Add remote SSRCs so we can distinguish which RTP packets actually
+ // belong to this pipeline (also RTCP sender reports).
+ for (uint32_t ssrc : mJsepTransceiver->mRecvTrack.GetSsrcs()) {
+ filter->AddRemoteSSRC(ssrc);
+ }
+ for (uint32_t ssrc : mJsepTransceiver->mRecvTrack.GetRtxSsrcs()) {
+ filter->AddRemoteSSRC(ssrc);
+ }
+ auto mid = Maybe<std::string>();
+ if (GetMid() != "") {
+ mid = Some(GetMid());
+ }
+ filter->SetRemoteMediaStreamId(mid);
+
+ // Add unique payload types as a last-ditch fallback
+ auto uniquePts = mJsepTransceiver->mRecvTrack.GetNegotiatedDetails()
+ ->GetUniquePayloadTypes();
+ for (unsigned char& uniquePt : uniquePts) {
+ filter->AddUniquePT(uniquePt);
+ }
+ }
+
+ mPipeline->UpdateTransport_m(mJsepTransceiver->mTransport.mTransportId,
+ std::move(filter));
+}
+
+nsresult RTCRtpReceiver::UpdateConduit() {
+ if (mPipeline->Conduit()->type() == MediaSessionConduit::VIDEO) {
+ return UpdateVideoConduit();
+ }
+ return UpdateAudioConduit();
+}
+
+nsresult RTCRtpReceiver::UpdateVideoConduit() {
+ RefPtr<VideoSessionConduit> conduit =
+ static_cast<VideoSessionConduit*>(mPipeline->Conduit());
+
+ // NOTE(pkerr) - this is new behavior. Needed because the
+ // CreateVideoReceiveStream method of the Call API will assert (in debug)
+ // and fail if a value is not provided for the remote_ssrc that will be used
+ // by the far-end sender.
+ if (!mJsepTransceiver->mRecvTrack.GetSsrcs().empty()) {
+ MOZ_LOG(gReceiverLog, LogLevel::Debug,
+ ("%s[%s]: %s Setting remote SSRC %u", mPCHandle.c_str(),
+ GetMid().c_str(), __FUNCTION__,
+ mJsepTransceiver->mRecvTrack.GetSsrcs().front()));
+ uint32_t rtxSsrc = mJsepTransceiver->mRecvTrack.GetRtxSsrcs().empty()
+ ? 0
+ : mJsepTransceiver->mRecvTrack.GetRtxSsrcs().front();
+ conduit->SetRemoteSSRC(mJsepTransceiver->mRecvTrack.GetSsrcs().front(),
+ rtxSsrc);
+ }
+
+ // TODO (bug 1423041) once we pay attention to receiving MID's in RTP
+ // packets (see bug 1405495) we could make this depending on the presence of
+ // MID in the RTP packets instead of relying on the signaling.
+ if (mJsepTransceiver->HasBundleLevel() &&
+ (!mJsepTransceiver->mRecvTrack.GetNegotiatedDetails() ||
+ !mJsepTransceiver->mRecvTrack.GetNegotiatedDetails()->GetExt(
+ webrtc::RtpExtension::kMIdUri))) {
+ mStsThread->Dispatch(
+ NewRunnableMethod("VideoSessionConduit::DisableSsrcChanges", conduit,
+ &VideoSessionConduit::DisableSsrcChanges));
+ }
+
+ if (mJsepTransceiver->mRecvTrack.GetNegotiatedDetails() &&
+ mJsepTransceiver->mRecvTrack.GetActive()) {
+ const auto& details(*mJsepTransceiver->mRecvTrack.GetNegotiatedDetails());
+
+ TransceiverImpl::UpdateConduitRtpExtmap(
+ *conduit, details, MediaSessionConduitLocalDirection::kRecv);
+
+ std::vector<UniquePtr<VideoCodecConfig>> configs;
+ nsresult rv = TransceiverImpl::NegotiatedDetailsToVideoCodecConfigs(
+ details, &configs);
+
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gReceiverLog, LogLevel::Error,
+ ("%s[%s]: %s Failed to convert "
+ "JsepCodecDescriptions to VideoCodecConfigs (recv).",
+ mPCHandle.c_str(), GetMid().c_str(), __FUNCTION__));
+ return rv;
+ }
+
+ auto error =
+ conduit->ConfigureRecvMediaCodecs(configs, details.GetRtpRtcpConfig());
+
+ if (error) {
+ MOZ_LOG(gReceiverLog, LogLevel::Error,
+ ("%s[%s]: %s "
+ "ConfigureRecvMediaCodecs failed: %u",
+ mPCHandle.c_str(), GetMid().c_str(), __FUNCTION__, error));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult RTCRtpReceiver::UpdateAudioConduit() {
+ RefPtr<AudioSessionConduit> conduit =
+ static_cast<AudioSessionConduit*>(mPipeline->Conduit());
+
+ if (!mJsepTransceiver->mRecvTrack.GetSsrcs().empty()) {
+ MOZ_LOG(gReceiverLog, LogLevel::Debug,
+ ("%s[%s]: %s Setting remote SSRC %u", mPCHandle.c_str(),
+ GetMid().c_str(), __FUNCTION__,
+ mJsepTransceiver->mRecvTrack.GetSsrcs().front()));
+ uint32_t rtxSsrc = mJsepTransceiver->mRecvTrack.GetRtxSsrcs().empty()
+ ? 0
+ : mJsepTransceiver->mRecvTrack.GetRtxSsrcs().front();
+ conduit->SetRemoteSSRC(mJsepTransceiver->mRecvTrack.GetSsrcs().front(),
+ rtxSsrc);
+ }
+
+ if (mJsepTransceiver->mRecvTrack.GetNegotiatedDetails() &&
+ mJsepTransceiver->mRecvTrack.GetActive()) {
+ const auto& details(*mJsepTransceiver->mRecvTrack.GetNegotiatedDetails());
+ std::vector<UniquePtr<AudioCodecConfig>> configs;
+ nsresult rv = TransceiverImpl::NegotiatedDetailsToAudioCodecConfigs(
+ details, &configs);
+
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gReceiverLog, LogLevel::Error,
+ ("%s[%s]: %s Failed to convert "
+ "JsepCodecDescriptions to AudioCodecConfigs (recv).",
+ mPCHandle.c_str(), GetMid().c_str(), __FUNCTION__));
+ return rv;
+ }
+
+ // Ensure conduit knows about extensions prior to creating streams
+ TransceiverImpl::UpdateConduitRtpExtmap(
+ *conduit, details, MediaSessionConduitLocalDirection::kRecv);
+
+ auto error = conduit->ConfigureRecvMediaCodecs(configs);
+
+ if (error) {
+ MOZ_LOG(gReceiverLog, LogLevel::Error,
+ ("%s[%s]: %s "
+ "ConfigureRecvMediaCodecs failed: %u",
+ mPCHandle.c_str(), GetMid().c_str(), __FUNCTION__, error));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+void RTCRtpReceiver::Stop() {
+ if (mPipeline) {
+ mPipeline->Stop();
+ }
+}
+
+void RTCRtpReceiver::Start() {
+ mPipeline->Start();
+ mHaveStartedReceiving = true;
+}
+
+bool RTCRtpReceiver::HasTrack(const dom::MediaStreamTrack* aTrack) const {
+ return !aTrack || (mTrack == aTrack);
+}
+
+void RTCRtpReceiver::UpdateStreams(StreamAssociationChanges* aChanges) {
+ // We don't sort and use set_difference, because we need to report the
+ // added/removed streams in the order that they appear in the SDP.
+ std::set<std::string> newIds(
+ mJsepTransceiver->mRecvTrack.GetStreamIds().begin(),
+ mJsepTransceiver->mRecvTrack.GetStreamIds().end());
+ MOZ_ASSERT(mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit() ||
+ newIds.empty());
+ bool needsTrackEvent = false;
+ for (const auto& id : mStreamIds) {
+ if (!newIds.count(id)) {
+ aChanges->mStreamAssociationsRemoved.push_back({mTrack, id});
+ }
+ }
+
+ std::set<std::string> oldIds(mStreamIds.begin(), mStreamIds.end());
+ for (const auto& id : mJsepTransceiver->mRecvTrack.GetStreamIds()) {
+ if (!oldIds.count(id)) {
+ needsTrackEvent = true;
+ aChanges->mStreamAssociationsAdded.push_back({mTrack, id});
+ }
+ }
+
+ mStreamIds = mJsepTransceiver->mRecvTrack.GetStreamIds();
+
+ if (mRemoteSetSendBit != mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit()) {
+ mRemoteSetSendBit = mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit();
+ if (mRemoteSetSendBit) {
+ needsTrackEvent = true;
+ } else {
+ aChanges->mTracksToMute.push_back(mTrack);
+ }
+ }
+
+ if (needsTrackEvent) {
+ aChanges->mTrackEvents.push_back({this, mStreamIds});
+ }
+}
+
+void RTCRtpReceiver::MozAddRIDExtension(unsigned short aExtensionId) {
+ if (mPipeline) {
+ mPipeline->AddRIDExtension_m(aExtensionId);
+ }
+}
+
+void RTCRtpReceiver::MozAddRIDFilter(const nsAString& aRid) {
+ if (mPipeline) {
+ mPipeline->AddRIDFilter_m(NS_ConvertUTF16toUTF8(aRid).get());
+ }
+}
+
+// test-only: adds fake CSRCs and audio data
+void RTCRtpReceiver::MozInsertAudioLevelForContributingSource(
+ const uint32_t aSource, const DOMHighResTimeStamp aTimestamp,
+ const uint32_t aRtpTimestamp, const bool aHasLevel, const uint8_t aLevel) {
+ if (!mPipeline || mPipeline->IsVideo() || !mPipeline->Conduit()) {
+ return;
+ }
+ WebrtcAudioConduit* audio_conduit =
+ static_cast<WebrtcAudioConduit*>(mPipeline->Conduit());
+ audio_conduit->InsertAudioLevelForContributingSource(
+ aSource, aTimestamp, aRtpTimestamp, aHasLevel, aLevel);
+}
+
+void RTCRtpReceiver::OnRtcpBye() { SetReceiveTrackMuted(true); }
+
+void RTCRtpReceiver::OnRtcpTimeout() { SetReceiveTrackMuted(true); }
+
+void RTCRtpReceiver::SetReceiveTrackMuted(bool aMuted) {
+ if (mTrack) {
+ // This sets the muted state for mTrack and all its clones.
+ static_cast<RemoteTrackSource&>(mTrack->GetSource()).SetMuted(aMuted);
+ }
+}
+
+std::string RTCRtpReceiver::GetMid() const {
+ if (mJsepTransceiver->IsAssociated()) {
+ return mJsepTransceiver->GetMid();
+ }
+ return std::string();
+}
+
+} // namespace mozilla::dom
+
+#undef LOGTAG
diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.h b/dom/media/webrtc/jsapi/RTCRtpReceiver.h
new file mode 100644
index 0000000000..61ebf3b8f2
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.h
@@ -0,0 +1,137 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _RTCRtpReceiver_h_
+#define _RTCRtpReceiver_h_
+
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Maybe.h"
+#include "js/RootingAPI.h"
+#include "nsTArray.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "RTCStatsReport.h"
+#include "libwebrtcglue/RtcpEventObserver.h"
+#include <vector>
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+class MediaPipelineReceive;
+class MediaSessionConduit;
+class MediaTransportHandler;
+class JsepTransceiver;
+class TransceiverImpl;
+
+namespace dom {
+class MediaStreamTrack;
+class Promise;
+class RTCDtlsTransport;
+struct RTCRtpContributingSource;
+struct RTCRtpSynchronizationSource;
+
+class RTCRtpReceiver : public nsISupports,
+ public nsWrapperCache,
+ public RtcpEventObserver {
+ public:
+ explicit RTCRtpReceiver(nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded,
+ const std::string& aPCHandle,
+ MediaTransportHandler* aTransportHandler,
+ JsepTransceiver* aJsepTransceiver,
+ nsISerialEventTarget* aMainThread,
+ nsISerialEventTarget* aStsThread,
+ MediaSessionConduit* aConduit,
+ TransceiverImpl* aTransceiverImpl);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(RTCRtpReceiver)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // webidl
+ MediaStreamTrack* Track() const { return mTrack; }
+ RTCDtlsTransport* GetTransport() const;
+ already_AddRefed<Promise> GetStats();
+ void GetContributingSources(
+ nsTArray<dom::RTCRtpContributingSource>& aSources);
+ void GetSynchronizationSources(
+ nsTArray<dom::RTCRtpSynchronizationSource>& aSources);
+ // test-only: called from simulcast mochitests.
+ void MozAddRIDExtension(unsigned short aExtensionId);
+ void MozAddRIDFilter(const nsAString& aRid);
+ // test-only: insert fake CSRCs and audio levels for testing
+ void MozInsertAudioLevelForContributingSource(
+ const uint32_t aSource, const DOMHighResTimeStamp aTimestamp,
+ const uint32_t aRtpTimestamp, const bool aHasLevel, const uint8_t aLevel);
+
+ nsPIDOMWindowInner* GetParentObject() const;
+ nsTArray<RefPtr<RTCStatsPromise>> GetStatsInternal();
+
+ void Shutdown();
+ void Stop();
+ void Start();
+ bool HasTrack(const dom::MediaStreamTrack* aTrack) const;
+
+ struct StreamAssociation {
+ RefPtr<MediaStreamTrack> mTrack;
+ std::string mStreamId;
+ };
+
+ struct TrackEventInfo {
+ RefPtr<RTCRtpReceiver> mReceiver;
+ std::vector<std::string> mStreamIds;
+ };
+
+ struct StreamAssociationChanges {
+ std::vector<RefPtr<MediaStreamTrack>> mTracksToMute;
+ std::vector<StreamAssociation> mStreamAssociationsRemoved;
+ std::vector<StreamAssociation> mStreamAssociationsAdded;
+ std::vector<TrackEventInfo> mTrackEvents;
+ };
+
+ // This is called when we set an answer (ie; when the transport is finalized).
+ void UpdateTransport();
+ nsresult UpdateConduit();
+
+ // This is called when we set a remote description; may be an offer or answer.
+ void UpdateStreams(StreamAssociationChanges* aChanges);
+
+ void OnRtcpBye() override;
+
+ void OnRtcpTimeout() override;
+
+ void SetReceiveTrackMuted(bool aMuted);
+
+ private:
+ virtual ~RTCRtpReceiver();
+
+ nsresult UpdateVideoConduit();
+ nsresult UpdateAudioConduit();
+
+ std::string GetMid() const;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ const std::string mPCHandle;
+ RefPtr<JsepTransceiver> mJsepTransceiver;
+ bool mHaveStartedReceiving = false;
+ bool mHaveSetupTransport = false;
+ nsCOMPtr<nsISerialEventTarget> mMainThread;
+ nsCOMPtr<nsISerialEventTarget> mStsThread;
+ RefPtr<dom::MediaStreamTrack> mTrack;
+ RefPtr<MediaPipelineReceive> mPipeline;
+ RefPtr<MediaTransportHandler> mTransportHandler;
+ RefPtr<TransceiverImpl> mTransceiverImpl;
+ // This is [[AssociatedRemoteMediaStreams]], basically. We do not keep the
+ // streams themselves here, because that would require this object to know
+ // where the stream list for the whole RTCPeerConnection lives..
+ std::vector<std::string> mStreamIds;
+ bool mRemoteSetSendBit = false;
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif // _RTCRtpReceiver_h_
diff --git a/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp
new file mode 100644
index 0000000000..488c65cf44
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+
+#include "RTCStatsIdGenerator.h"
+#include "mozilla/RandomNum.h"
+#include <iostream>
+namespace mozilla {
+
+RTCStatsIdGenerator::RTCStatsIdGenerator()
+ : mSalt(RandomUint64().valueOr(0xa5a5a5a5)), mCounter(0) {}
+
+nsString RTCStatsIdGenerator::Id(const nsString& aKey) {
+ if (!aKey.Length()) {
+ MOZ_ASSERT(aKey.Length(), "Stats IDs should never be empty.");
+ return aKey;
+ }
+ if (mAllocated.find(aKey) == mAllocated.end()) {
+ mAllocated[aKey] = Generate();
+ }
+ return mAllocated[aKey];
+}
+
+nsString RTCStatsIdGenerator::Generate() {
+ auto random = RandomUint64().valueOr(0x1a22);
+ auto idNum = static_cast<uint32_t>(mSalt ^ ((mCounter++ << 16) | random));
+ nsString id;
+ id.AppendInt(idNum, 16); // Append as hex
+ return id;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h
new file mode 100644
index 0000000000..0ce858e64b
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+
+#ifndef _RTCSTATSIDGENERATOR_H_
+#define _RTCSTATSIDGENERATOR_H_
+
+#include "mozilla/Atomics.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+
+#include <map>
+
+namespace mozilla {
+
+class RTCStatsIdGenerator {
+ public:
+ RTCStatsIdGenerator();
+ nsString Id(const nsString& aKey);
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RTCStatsIdGenerator);
+
+ private:
+ virtual ~RTCStatsIdGenerator(){};
+ nsString Generate();
+
+ const uint64_t mSalt;
+ uint64_t mCounter;
+ std::map<nsString, nsString> mAllocated;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/jsapi/RTCStatsReport.cpp b/dom/media/webrtc/jsapi/RTCStatsReport.cpp
new file mode 100644
index 0000000000..c32a000d11
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCStatsReport.cpp
@@ -0,0 +1,94 @@
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "RTCStatsReport.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceService.h"
+#include "nsRFPService.h"
+
+namespace mozilla::dom {
+
+RTCStatsTimestampMaker::RTCStatsTimestampMaker(const GlobalObject* aGlobal) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal->GetAsSupports());
+ if (window) {
+ mRandomTimelineSeed = window->GetPerformance()->GetRandomTimelineSeed();
+ mStartMonotonic = window->GetPerformance()->CreationTimeStamp();
+ // Ugh. Performance::TimeOrigin is not constant, which means we need to
+ // emulate this weird behavior so our time stamps are consistent with JS
+ // timeOrigin. This is based on the code here:
+ // https://searchfox.org/mozilla-central/rev/
+ // 053826b10f838f77c27507e5efecc96e34718541/dom/performance/Performance.cpp#111-117
+ mStartWallClockRaw =
+ PerformanceService::GetOrCreate()->TimeOrigin(mStartMonotonic);
+ MOZ_ASSERT(window->AsGlobal());
+ mCrossOriginIsolated = window->AsGlobal()->CrossOriginIsolated();
+ }
+}
+
+DOMHighResTimeStamp RTCStatsTimestampMaker::GetNow() const {
+ // webrtc-pc says to use performance.timeOrigin + performance.now(), but
+ // keeping a Performance object around is difficult because it is
+ // main-thread-only. So, we perform the same calculation here. Note that this
+ // can be very different from the current wall-clock time because of changes
+ // to the wall clock, or monotonic clock drift over long periods of time.
+ // We are very careful to do exactly what Performance does, to avoid timestamp
+ // discrepancies.
+ DOMHighResTimeStamp msSinceStart =
+ (TimeStamp::NowUnfuzzed() - mStartMonotonic).ToMilliseconds();
+ // mRandomTimelineSeed is not set in the unit-tests.
+ if (mRandomTimelineSeed) {
+ msSinceStart = nsRFPService::ReduceTimePrecisionAsMSecs(
+ msSinceStart, mRandomTimelineSeed, /* aIsSystemPrincipal */ false,
+ mCrossOriginIsolated);
+ }
+ return msSinceStart + nsRFPService::ReduceTimePrecisionAsMSecs(
+ mStartWallClockRaw, 0,
+ /* aIsSystemPrincipal */ false,
+ mCrossOriginIsolated);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCStatsReport, mParent)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(RTCStatsReport, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(RTCStatsReport, Release)
+
+RTCStatsReport::RTCStatsReport(nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {}
+
+/*static*/
+already_AddRefed<RTCStatsReport> RTCStatsReport::Constructor(
+ const GlobalObject& aGlobal) {
+ nsCOMPtr<nsPIDOMWindowInner> window(
+ do_QueryInterface(aGlobal.GetAsSupports()));
+ RefPtr<RTCStatsReport> report(new RTCStatsReport(window));
+ return report.forget();
+}
+
+JSObject* RTCStatsReport::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCStatsReport_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void RTCStatsReport::Incorporate(RTCStatsCollection& aStats) {
+ SetRTCStats(aStats.mIceCandidatePairStats);
+ SetRTCStats(aStats.mIceCandidateStats);
+ SetRTCStats(aStats.mInboundRtpStreamStats);
+ SetRTCStats(aStats.mOutboundRtpStreamStats);
+ SetRTCStats(aStats.mRemoteInboundRtpStreamStats);
+ SetRTCStats(aStats.mRemoteOutboundRtpStreamStats);
+ SetRTCStats(aStats.mRtpContributingSourceStats);
+ SetRTCStats(aStats.mTrickledIceCandidateStats);
+ SetRTCStats(aStats.mDataChannelStats);
+}
+
+void RTCStatsReport::Set(const nsAString& aKey, JS::Handle<JSObject*> aValue,
+ ErrorResult& aRv) {
+ RTCStatsReport_Binding::MaplikeHelpers::Set(this, aKey, aValue, aRv);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/RTCStatsReport.h b/dom/media/webrtc/jsapi/RTCStatsReport.h
new file mode 100644
index 0000000000..0de84114da
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCStatsReport.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef RTCStatsReport_h_
+#define RTCStatsReport_h_
+
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+
+#include "nsPIDOMWindow.h" // nsPIDOMWindowInner
+#include "mozilla/dom/ScriptSettings.h" // AutoEntryScript
+#include "nsIGlobalObject.h"
+#include "js/RootingAPI.h" // JS::Rooted
+#include "js/Value.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/UniquePtr.h"
+#include "prtime.h" // PR_Now
+#include "mozilla/MozPromise.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/RTCStatsReportBinding.h" // RTCStatsCollection
+
+namespace mozilla {
+namespace dom {
+
+class RTCStatsTimestampMaker {
+ public:
+ RTCStatsTimestampMaker() = default;
+ explicit RTCStatsTimestampMaker(const GlobalObject* aGlobal);
+ DOMHighResTimeStamp GetNow() const;
+
+ private:
+ uint64_t mRandomTimelineSeed = 0;
+ DOMHighResTimeStamp mStartWallClockRaw = (double)PR_Now() / PR_USEC_PER_MSEC;
+ TimeStamp mStartMonotonic = TimeStamp::NowUnfuzzed();
+ bool mCrossOriginIsolated = false;
+};
+
+// TODO(bug 1588303): If we ever get move semantics for webidl dictionaries, we
+// can stop wrapping these in UniquePtr, which will allow us to simplify code
+// in several places.
+typedef MozPromise<UniquePtr<RTCStatsCollection>, nsresult, true>
+ RTCStatsPromise;
+
+typedef MozPromise<UniquePtr<RTCStatsReportInternal>, nsresult, true>
+ RTCStatsReportPromise;
+
+class RTCStatsReport final : public nsWrapperCache {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(RTCStatsReport)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(RTCStatsReport)
+
+ explicit RTCStatsReport(nsPIDOMWindowInner* aParent);
+
+ // TODO(bug 1586109): Remove this once we no longer have to create empty
+ // RTCStatsReports from JS.
+ static already_AddRefed<RTCStatsReport> Constructor(
+ const GlobalObject& aGlobal);
+
+ void Incorporate(RTCStatsCollection& aStats);
+
+ nsPIDOMWindowInner* GetParentObject() const { return mParent; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ ~RTCStatsReport() = default;
+ void Set(const nsAString& aKey, JS::Handle<JSObject*> aValue,
+ ErrorResult& aRv);
+
+ template <typename T>
+ nsresult SetRTCStats(Sequence<T>& aValues) {
+ for (T& value : aValues) {
+ nsresult rv = SetRTCStats(value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return NS_OK;
+ }
+
+ // We cannot just declare this as SetRTCStats(RTCStats&), because the
+ // conversion function that ToJSValue uses is non-virtual.
+ template <typename T>
+ nsresult SetRTCStats(T& aValue) {
+ static_assert(std::is_base_of<RTCStats, T>::value,
+ "SetRTCStats is for setting RTCStats only");
+
+ if (!aValue.mId.WasPassed()) {
+ return NS_OK;
+ }
+
+ const nsString key(aValue.mId.Value());
+
+ // Cargo-culted from dom::Promise; converts aValue to a JSObject
+ AutoEntryScript aes(mParent->AsGlobal()->GetGlobalJSObject(),
+ "RTCStatsReport::SetRTCStats");
+ JSContext* cx = aes.cx();
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, std::forward<T>(aValue), &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::Rooted<JSObject*> jsObject(cx, &val.toObject());
+
+ ErrorResult rv;
+ Set(key, jsObject, rv);
+ return rv.StealNSResult();
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // RTCStatsReport_h_
diff --git a/dom/media/webrtc/jsapi/RemoteTrackSource.h b/dom/media/webrtc/jsapi/RemoteTrackSource.h
new file mode 100644
index 0000000000..05fae35646
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RemoteTrackSource.h
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _REMOTE_TRACK_SOURCE_H_
+#define _REMOTE_TRACK_SOURCE_H_
+
+#include "MediaStreamTrack.h"
+#include "MediaStreamError.h"
+
+namespace mozilla {
+
+class RemoteTrackSource : public dom::MediaStreamTrackSource {
+ public:
+ explicit RemoteTrackSource(SourceMediaTrack* aStream,
+ nsIPrincipal* aPrincipal, const nsString& aLabel)
+ : dom::MediaStreamTrackSource(aPrincipal, aLabel), mStream(aStream) {}
+
+ dom::MediaSourceEnum GetMediaSource() const override {
+ return dom::MediaSourceEnum::Other;
+ }
+
+ RefPtr<ApplyConstraintsPromise> ApplyConstraints(
+ const dom::MediaTrackConstraints& aConstraints,
+ dom::CallerType aCallerType) override {
+ return ApplyConstraintsPromise::CreateAndReject(
+ MakeRefPtr<MediaMgrError>(
+ dom::MediaStreamError::Name::OverconstrainedError, ""),
+ __func__);
+ }
+
+ void Stop() override {
+ // XXX (Bug 1314270): Implement rejection logic if necessary when we have
+ // clarity in the spec.
+ }
+
+ void Disable() override {}
+
+ void Enable() override {}
+
+ void SetPrincipal(nsIPrincipal* aPrincipal) {
+ mPrincipal = aPrincipal;
+ PrincipalChanged();
+ }
+ void SetMuted(bool aMuted) { MutedChanged(aMuted); }
+ void ForceEnded() { OverrideEnded(); }
+
+ const RefPtr<SourceMediaTrack> mStream;
+
+ protected:
+ virtual ~RemoteTrackSource() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mStream->IsDestroyed());
+ }
+};
+
+} // namespace mozilla
+
+#endif // _REMOTE_TRACK_SOURCE_H_
diff --git a/dom/media/webrtc/jsapi/TransceiverImpl.cpp b/dom/media/webrtc/jsapi/TransceiverImpl.cpp
new file mode 100644
index 0000000000..6db46af15e
--- /dev/null
+++ b/dom/media/webrtc/jsapi/TransceiverImpl.cpp
@@ -0,0 +1,900 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "jsapi/TransceiverImpl.h"
+#include "mozilla/UniquePtr.h"
+#include <string>
+#include <vector>
+#include "libwebrtcglue/AudioConduit.h"
+#include "libwebrtcglue/VideoConduit.h"
+#include "MediaTrackGraph.h"
+#include "transportbridge/MediaPipeline.h"
+#include "transportbridge/MediaPipelineFilter.h"
+#include "jsep/JsepTrack.h"
+#include "sdp/SdpHelper.h"
+#include "MediaTrackGraphImpl.h"
+#include "transport/logging.h"
+#include "MediaEngine.h"
+#include "nsIPrincipal.h"
+#include "MediaSegment.h"
+#include "RemoteTrackSource.h"
+#include "libwebrtcglue/RtpRtcpConfig.h"
+#include "MediaTransportHandler.h"
+#include "mozilla/dom/RTCRtpReceiverBinding.h"
+#include "mozilla/dom/RTCRtpSenderBinding.h"
+#include "mozilla/dom/RTCRtpTransceiverBinding.h"
+#include "mozilla/dom/TransceiverImplBinding.h"
+#include "RTCDtlsTransport.h"
+#include "RTCRtpReceiver.h"
+#include "RTCDTMFSender.h"
+#include "libwebrtcglue/WebrtcGmpVideoCodec.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+MOZ_MTLOG_MODULE("transceiverimpl")
+
+using LocalDirection = MediaSessionConduitLocalDirection;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransceiverImpl, mWindow, mSendTrack,
+ mReceiver, mDtmf, mDtlsTransport,
+ mLastStableDtlsTransport)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TransceiverImpl)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TransceiverImpl)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransceiverImpl)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TransceiverImpl::TransceiverImpl(
+ nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded,
+ const std::string& aPCHandle, MediaTransportHandler* aTransportHandler,
+ JsepTransceiver* aJsepTransceiver, nsISerialEventTarget* aMainThread,
+ nsISerialEventTarget* aStsThread, dom::MediaStreamTrack* aSendTrack,
+ WebRtcCallWrapper* aCallWrapper)
+ : mWindow(aWindow),
+ mPCHandle(aPCHandle),
+ mTransportHandler(aTransportHandler),
+ mJsepTransceiver(aJsepTransceiver),
+ mHaveSetupTransport(false),
+ mMainThread(aMainThread),
+ mStsThread(aStsThread),
+ mSendTrack(aSendTrack),
+ mCallWrapper(aCallWrapper) {
+ if (IsVideo()) {
+ InitVideo();
+ } else {
+ InitAudio();
+ }
+
+ if (!IsValid()) {
+ return;
+ }
+
+ mConduit->SetPCHandle(mPCHandle);
+
+ mReceiver = new RTCRtpReceiver(aWindow, aPrivacyNeeded, aPCHandle,
+ aTransportHandler, aJsepTransceiver,
+ aMainThread, aStsThread, mConduit, this);
+
+ if (!IsVideo()) {
+ mDtmf = new RTCDTMFSender(
+ aWindow, this, static_cast<AudioSessionConduit*>(mConduit.get()));
+ }
+
+ mTransmitPipeline =
+ new MediaPipelineTransmit(mPCHandle, mTransportHandler, mMainThread.get(),
+ mStsThread.get(), IsVideo(), mConduit);
+
+ mTransmitPipeline->SetTrack(mSendTrack);
+
+ auto self = nsMainThreadPtrHandle<TransceiverImpl>(
+ new nsMainThreadPtrHolder<TransceiverImpl>(
+ "TransceiverImpl::TransceiverImpl::self", this, false));
+ mStsThread->Dispatch(
+ NS_NewRunnableFunction("TransceiverImpl::TransceiverImpl", [self] {
+ self->mTransportHandler->SignalStateChange.connect(
+ self.get(), &TransceiverImpl::UpdateDtlsTransportState);
+ self->mTransportHandler->SignalRtcpStateChange.connect(
+ self.get(), &TransceiverImpl::UpdateDtlsTransportState);
+ }));
+}
+
+TransceiverImpl::~TransceiverImpl() = default;
+
+void TransceiverImpl::SetDtlsTransport(dom::RTCDtlsTransport* aDtlsTransport,
+ bool aStable) {
+ mDtlsTransport = aDtlsTransport;
+ if (aStable) {
+ mLastStableDtlsTransport = mDtlsTransport;
+ }
+}
+
+void TransceiverImpl::RollbackToStableDtlsTransport() {
+ mDtlsTransport = mLastStableDtlsTransport;
+}
+
+void TransceiverImpl::UpdateDtlsTransportState(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ if (!mMainThread->IsOnCurrentThread()) {
+ mMainThread->Dispatch(
+ WrapRunnable(this, &TransceiverImpl::UpdateDtlsTransportState,
+ aTransportId, aState),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (!mDtlsTransport) {
+ return;
+ }
+
+ mDtlsTransport->UpdateState(aState);
+}
+
+void TransceiverImpl::InitAudio() {
+ mConduit = AudioSessionConduit::Create(mCallWrapper, mStsThread);
+
+ if (!mConduit) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__
+ << ": Failed to create AudioSessionConduit");
+ // TODO(bug 1422897): We need a way to record this when it happens in the
+ // wild.
+ }
+}
+
+void TransceiverImpl::InitVideo() {
+ mConduit = VideoSessionConduit::Create(mCallWrapper, mStsThread);
+
+ if (!mConduit) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__
+ << ": Failed to create VideoSessionConduit");
+ // TODO(bug 1422897): We need a way to record this when it happens in the
+ // wild.
+ }
+}
+
+nsresult TransceiverImpl::UpdateSinkIdentity(
+ const dom::MediaStreamTrack* aTrack, nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity) {
+ if (mJsepTransceiver->IsStopped()) {
+ return NS_OK;
+ }
+
+ mTransmitPipeline->UpdateSinkIdentity_m(aTrack, aPrincipal, aSinkIdentity);
+ return NS_OK;
+}
+
+void TransceiverImpl::Shutdown_m() {
+ // Called via PCImpl::Close -> PCImpl::CloseInt -> PCImpl::ShutdownMedia ->
+ // PCMedia::SelfDestruct. Satisfies step 7 of
+ // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-close
+ if (mDtlsTransport) {
+ mDtlsTransport->UpdateState(TransportLayer::TS_CLOSED);
+ }
+ Stop();
+ mTransmitPipeline = nullptr;
+ auto self = nsMainThreadPtrHandle<TransceiverImpl>(
+ new nsMainThreadPtrHolder<TransceiverImpl>(
+ "TransceiverImpl::Shutdown_m::self", this, false));
+ mStsThread->Dispatch(NS_NewRunnableFunction(__func__, [self] {
+ self->disconnect_all();
+ self->mTransportHandler = nullptr;
+ }));
+}
+
+nsresult TransceiverImpl::UpdateSendTrack(dom::MediaStreamTrack* aSendTrack) {
+ if (mJsepTransceiver->IsStopped()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ << "("
+ << aSendTrack << ")");
+ mSendTrack = aSendTrack;
+ return mTransmitPipeline->SetTrack(mSendTrack);
+}
+
+nsresult TransceiverImpl::UpdateTransport() {
+ if (!mJsepTransceiver->HasLevel() || mJsepTransceiver->IsStopped()) {
+ return NS_OK;
+ }
+
+ mReceiver->UpdateTransport();
+
+ if (!mHaveSetupTransport) {
+ mTransmitPipeline->SetLevel(mJsepTransceiver->GetLevel());
+ mHaveSetupTransport = true;
+ }
+
+ mTransmitPipeline->UpdateTransport_m(
+ mJsepTransceiver->mTransport.mTransportId, nullptr);
+ return NS_OK;
+}
+
+nsresult TransceiverImpl::UpdateConduit() {
+ if (mJsepTransceiver->IsStopped()) {
+ return NS_OK;
+ }
+
+ if (mJsepTransceiver->IsAssociated()) {
+ mMid = mJsepTransceiver->GetMid();
+ } else {
+ mMid.clear();
+ }
+
+ mReceiver->Stop();
+
+ mTransmitPipeline->Stop();
+
+ // NOTE(pkerr) - the Call API requires the both local_ssrc and remote_ssrc be
+ // set to a non-zero value or the CreateVideo...Stream call will fail.
+ if (mJsepTransceiver->mSendTrack.GetSsrcs().empty()) {
+ MOZ_MTLOG(ML_ERROR,
+ mPCHandle << "[" << mMid << "]: " << __FUNCTION__
+ << " No local SSRC set! (Should be set regardless of "
+ "whether we're sending RTP; we need a local SSRC in "
+ "all cases)");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mConduit->SetLocalSSRCs(mJsepTransceiver->mSendTrack.GetSsrcs(),
+ mJsepTransceiver->mSendTrack.GetRtxSsrcs())) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__
+ << " SetLocalSSRCs failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ mConduit->SetLocalCNAME(mJsepTransceiver->mSendTrack.GetCNAME().c_str());
+ mConduit->SetLocalMID(mMid);
+
+ nsresult rv;
+
+ mReceiver->UpdateConduit();
+
+ // TODO(bug 1616937): Move this stuff into RTCRtpSender.
+ if (IsVideo()) {
+ rv = UpdateVideoConduit();
+ } else {
+ rv = UpdateAudioConduit();
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (mJsepTransceiver->mRecvTrack.GetActive()) {
+ mReceiver->Start();
+ }
+
+ if (mJsepTransceiver->mSendTrack.GetActive()) {
+ if (!mSendTrack) {
+ MOZ_MTLOG(ML_WARNING,
+ mPCHandle << "[" << mMid << "]: " << __FUNCTION__
+ << " Starting transmit conduit without send track!");
+ }
+ mTransmitPipeline->Start();
+ }
+
+ return NS_OK;
+}
+
+void TransceiverImpl::ResetSync() {
+ if (mConduit) {
+ mConduit->SetSyncGroup("");
+ }
+}
+
+nsresult TransceiverImpl::SyncWithMatchingVideoConduits(
+ std::vector<RefPtr<TransceiverImpl>>& transceivers) {
+ if (mJsepTransceiver->IsStopped()) {
+ return NS_OK;
+ }
+
+ if (IsVideo()) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__
+ << " called when transceiver is not "
+ "video! This should never happen.");
+ MOZ_CRASH();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ std::set<std::string> myReceiveStreamIds;
+ myReceiveStreamIds.insert(mJsepTransceiver->mRecvTrack.GetStreamIds().begin(),
+ mJsepTransceiver->mRecvTrack.GetStreamIds().end());
+
+ for (RefPtr<TransceiverImpl>& transceiver : transceivers) {
+ if (!transceiver->IsValid()) {
+ continue;
+ }
+
+ if (!transceiver->IsVideo()) {
+ // |this| is an audio transceiver, so we skip other audio transceivers
+ continue;
+ }
+
+ // Maybe could make this more efficient by cacheing this set, but probably
+ // not worth it.
+ for (const std::string& streamId :
+ transceiver->mJsepTransceiver->mRecvTrack.GetStreamIds()) {
+ if (myReceiveStreamIds.count(streamId)) {
+ // Ok, we have one video, one non-video - cross the streams!
+ mConduit->SetSyncGroup(streamId);
+ transceiver->mConduit->SetSyncGroup(streamId);
+
+ MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__
+ << " Syncing " << mConduit.get() << " to "
+ << transceiver->mConduit.get());
+
+ // The sync code in call.cc only permits sync between audio stream and
+ // one video stream. They take the first match, so there's no point in
+ // continuing here. If we want to change the default, we should sort
+ // video streams here and only call SetSyncGroup on the chosen stream.
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool TransceiverImpl::ConduitHasPluginID(uint64_t aPluginID) {
+ return mConduit ? mConduit->CodecPluginID() == aPluginID : false;
+}
+
+bool TransceiverImpl::HasSendTrack(
+ const dom::MediaStreamTrack* aSendTrack) const {
+ if (!mSendTrack) {
+ return false;
+ }
+
+ if (!aSendTrack) {
+ return true;
+ }
+
+ return mSendTrack.get() == aSendTrack;
+}
+
+void TransceiverImpl::SyncWithJS(dom::RTCRtpTransceiver& aJsTransceiver,
+ ErrorResult& aRv) {
+ MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__
+ << " Syncing with JS transceiver");
+
+ if (!mTransmitPipeline) {
+ // Shutdown_m has already been called, probably due to pc.close(). Just
+ // nod and smile.
+ return;
+ }
+
+ // Update stopped, both ways, since either JSEP or JS can stop these
+ if (mJsepTransceiver->IsStopped()) {
+ // We don't call RTCRtpTransceiver::Stop(), because that causes another sync
+ aJsTransceiver.SetStopped(aRv);
+ Stop();
+ } else if (aJsTransceiver.GetStopped(aRv)) {
+ mJsepTransceiver->Stop();
+ Stop();
+ }
+
+ // Lots of this in here for simple getters that should never fail. Lame.
+ // Just propagate the exception and let JS log it.
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Update direction from JS only
+ dom::RTCRtpTransceiverDirection direction = aJsTransceiver.GetDirection(aRv);
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ switch (direction) {
+ case dom::RTCRtpTransceiverDirection::Sendrecv:
+ mJsepTransceiver->mJsDirection =
+ SdpDirectionAttribute::Direction::kSendrecv;
+ break;
+ case dom::RTCRtpTransceiverDirection::Sendonly:
+ mJsepTransceiver->mJsDirection =
+ SdpDirectionAttribute::Direction::kSendonly;
+ break;
+ case dom::RTCRtpTransceiverDirection::Recvonly:
+ mJsepTransceiver->mJsDirection =
+ SdpDirectionAttribute::Direction::kRecvonly;
+ break;
+ case dom::RTCRtpTransceiverDirection::Inactive:
+ mJsepTransceiver->mJsDirection =
+ SdpDirectionAttribute::Direction::kInactive;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ aRv = NS_ERROR_INVALID_ARG;
+ return;
+ }
+
+ // Update send track ids in JSEP
+ RefPtr<dom::RTCRtpSender> sender = aJsTransceiver.GetSender(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ nsTArray<RefPtr<DOMMediaStream>> streams;
+ sender->GetStreams(streams, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ std::vector<std::string> streamIds;
+ for (const auto& stream : streams) {
+ nsString wideStreamId;
+ stream->GetId(wideStreamId);
+ std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get();
+ MOZ_ASSERT(!streamId.empty());
+ streamIds.push_back(streamId);
+ }
+
+ mJsepTransceiver->mSendTrack.UpdateStreamIds(streamIds);
+
+ // Update RTCRtpParameters
+ // TODO: Both ways for things like ssrc, codecs, header extensions, etc
+
+ dom::RTCRtpParameters parameters;
+ sender->GetParameters(parameters, aRv);
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ std::vector<JsepTrack::JsConstraints> constraints;
+
+ if (parameters.mEncodings.WasPassed()) {
+ for (auto& encoding : parameters.mEncodings.Value()) {
+ JsepTrack::JsConstraints constraint;
+ if (encoding.mRid.WasPassed()) {
+ // TODO: Either turn on the RID RTP header extension in JsepSession, or
+ // just leave that extension on all the time?
+ constraint.rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get();
+ }
+ if (encoding.mMaxBitrate.WasPassed()) {
+ constraint.constraints.maxBr = encoding.mMaxBitrate.Value();
+ }
+ constraint.constraints.scaleDownBy = encoding.mScaleResolutionDownBy;
+ constraints.push_back(constraint);
+ }
+ }
+
+ if (mJsepTransceiver->mSendTrack.SetJsConstraints(constraints)) {
+ if (mTransmitPipeline->Transmitting()) {
+ WebrtcGmpPCHandleSetter setter(mPCHandle);
+ DebugOnly<nsresult> rv = UpdateConduit();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // If a SRD has unset the receive bit, stop the receive pipeline so incoming
+ // RTP does not unmute the receive track.
+ if (!mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit() ||
+ !mJsepTransceiver->mRecvTrack.GetActive()) {
+ mReceiver->Stop();
+ }
+
+ // mid from JSEP
+ if (mJsepTransceiver->IsAssociated()) {
+ aJsTransceiver.SetMid(
+ NS_ConvertUTF8toUTF16(mJsepTransceiver->GetMid().c_str()), aRv);
+ } else {
+ aJsTransceiver.UnsetMid(aRv);
+ }
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // currentDirection from JSEP, but not if "this transceiver has never been
+ // represented in an offer/answer exchange"
+ if (mJsepTransceiver->HasLevel() && mJsepTransceiver->IsNegotiated()) {
+ if (IsReceiving()) {
+ if (IsSending()) {
+ aJsTransceiver.SetCurrentDirection(
+ dom::RTCRtpTransceiverDirection::Sendrecv, aRv);
+ } else {
+ aJsTransceiver.SetCurrentDirection(
+ dom::RTCRtpTransceiverDirection::Recvonly, aRv);
+ }
+ } else {
+ if (IsSending()) {
+ aJsTransceiver.SetCurrentDirection(
+ dom::RTCRtpTransceiverDirection::Sendonly, aRv);
+ } else {
+ aJsTransceiver.SetCurrentDirection(
+ dom::RTCRtpTransceiverDirection::Inactive, aRv);
+ }
+ }
+
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // AddTrack magic from JS
+ if (aJsTransceiver.GetAddTrackMagic(aRv)) {
+ mJsepTransceiver->SetAddTrackMagic();
+ }
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (mJsepTransceiver->IsRemoved()) {
+ aJsTransceiver.SetShouldRemove(true, aRv);
+ }
+}
+
+bool TransceiverImpl::CanSendDTMF() const {
+ // Spec says: "If connection's RTCPeerConnectionState is not "connected"
+ // return false." We don't support that right now. This is supposed to be
+ // true once ICE is complete, and _all_ DTLS handshakes are also complete. We
+ // don't really have access to the state of _all_ of our DTLS states either.
+ // Our pipeline _does_ know whether SRTP/SRTCP is ready, which happens
+ // immediately after our transport finishes DTLS (unless there was an error),
+ // so this is pretty close.
+ // TODO (bug 1265827): Base this on RTCPeerConnectionState instead.
+ // TODO (bug 1623193): Tighten this up
+ if (!IsSending() || !mSendTrack) {
+ return false;
+ }
+
+ // Ok, it looks like the connection is up and sending. Did we negotiate
+ // telephone-event?
+ JsepTrackNegotiatedDetails* details =
+ mJsepTransceiver->mSendTrack.GetNegotiatedDetails();
+ if (NS_WARN_IF(!details || !details->GetEncodingCount())) {
+ // What?
+ return false;
+ }
+
+ for (size_t i = 0; i < details->GetEncodingCount(); ++i) {
+ const auto& encoding = details->GetEncoding(i);
+ for (const auto& codec : encoding.GetCodecs()) {
+ if (codec->mName == "telephone-event") {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+JSObject* TransceiverImpl::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::TransceiverImpl_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TransceiverImpl::GetParentObject() const { return mWindow; }
+
+RefPtr<MediaPipelineTransmit> TransceiverImpl::GetSendPipeline() {
+ return mTransmitPipeline;
+}
+
+static nsresult JsepCodecDescToAudioCodecConfig(
+ const JsepCodecDescription& aCodec, UniquePtr<AudioCodecConfig>* aConfig) {
+ MOZ_ASSERT(aCodec.mType == SdpMediaSection::kAudio);
+ if (aCodec.mType != SdpMediaSection::kAudio) return NS_ERROR_INVALID_ARG;
+
+ const JsepAudioCodecDescription& desc =
+ static_cast<const JsepAudioCodecDescription&>(aCodec);
+
+ uint16_t pt;
+
+ if (!desc.GetPtAsInt(&pt)) {
+ MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << desc.mDefaultPt);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aConfig->reset(new AudioCodecConfig(pt, desc.mName, desc.mClock,
+ desc.mForceMono ? 1 : desc.mChannels,
+ desc.mFECEnabled));
+ (*aConfig)->mMaxPlaybackRate = desc.mMaxPlaybackRate;
+ (*aConfig)->mDtmfEnabled = desc.mDtmfEnabled;
+ (*aConfig)->mDTXEnabled = desc.mDTXEnabled;
+ (*aConfig)->mMaxAverageBitrate = desc.mMaxAverageBitrate;
+ (*aConfig)->mFrameSizeMs = desc.mFrameSizeMs;
+ (*aConfig)->mMinFrameSizeMs = desc.mMinFrameSizeMs;
+ (*aConfig)->mMaxFrameSizeMs = desc.mMaxFrameSizeMs;
+ (*aConfig)->mCbrEnabled = desc.mCbrEnabled;
+
+ return NS_OK;
+}
+
+// TODO: Maybe move this someplace else?
+/*static*/
+nsresult TransceiverImpl::NegotiatedDetailsToAudioCodecConfigs(
+ const JsepTrackNegotiatedDetails& aDetails,
+ std::vector<UniquePtr<AudioCodecConfig>>* aConfigs) {
+ UniquePtr<AudioCodecConfig> telephoneEvent;
+
+ if (aDetails.GetEncodingCount()) {
+ for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) {
+ UniquePtr<AudioCodecConfig> config;
+ if (NS_FAILED(JsepCodecDescToAudioCodecConfig(*codec, &config))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (config->mName == "telephone-event") {
+ telephoneEvent = std::move(config);
+ } else {
+ aConfigs->push_back(std::move(config));
+ }
+ }
+ }
+
+ // Put telephone event at the back, because webrtc.org crashes if we don't
+ // If we need to do even more sorting, we should use std::sort.
+ if (telephoneEvent) {
+ aConfigs->push_back(std::move(telephoneEvent));
+ }
+
+ if (aConfigs->empty()) {
+ MOZ_MTLOG(ML_ERROR, "Can't set up a conduit with 0 codecs");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult TransceiverImpl::UpdateAudioConduit() {
+ MOZ_ASSERT(IsValid());
+
+ RefPtr<AudioSessionConduit> conduit =
+ static_cast<AudioSessionConduit*>(mConduit.get());
+
+ if (mJsepTransceiver->mSendTrack.GetNegotiatedDetails() &&
+ mJsepTransceiver->mSendTrack.GetActive()) {
+ const auto& details(*mJsepTransceiver->mSendTrack.GetNegotiatedDetails());
+ std::vector<UniquePtr<AudioCodecConfig>> configs;
+ nsresult rv = TransceiverImpl::NegotiatedDetailsToAudioCodecConfigs(
+ details, &configs);
+
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle
+ << "[" << mMid << "]: " << __FUNCTION__
+ << " Failed to convert JsepCodecDescriptions to "
+ "AudioCodecConfigs (send).");
+ return rv;
+ }
+
+ for (const auto& value : configs) {
+ if (value->mName == "telephone-event") {
+ // we have a telephone event codec, so we need to make sure
+ // the dynamic pt is set properly
+ conduit->SetDtmfPayloadType(value->mType, value->mFreq);
+ break;
+ }
+ }
+
+ auto error = conduit->ConfigureSendMediaCodec(configs[0].get());
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle
+ << "[" << mMid << "]: " << __FUNCTION__
+ << " ConfigureSendMediaCodec failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ UpdateConduitRtpExtmap(*conduit, details, LocalDirection::kSend);
+ }
+
+ return NS_OK;
+}
+
+static nsresult JsepCodecDescToVideoCodecConfig(
+ const JsepCodecDescription& aCodec, UniquePtr<VideoCodecConfig>* aConfig) {
+ MOZ_ASSERT(aCodec.mType == SdpMediaSection::kVideo);
+ if (aCodec.mType != SdpMediaSection::kVideo) {
+ MOZ_ASSERT(false, "JsepCodecDescription has wrong type");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const JsepVideoCodecDescription& desc =
+ static_cast<const JsepVideoCodecDescription&>(aCodec);
+
+ uint16_t pt;
+
+ if (!desc.GetPtAsInt(&pt)) {
+ MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << desc.mDefaultPt);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ UniquePtr<VideoCodecConfigH264> h264Config;
+
+ if (desc.mName == "H264") {
+ h264Config = MakeUnique<VideoCodecConfigH264>();
+ size_t spropSize = sizeof(h264Config->sprop_parameter_sets);
+ strncpy(h264Config->sprop_parameter_sets, desc.mSpropParameterSets.c_str(),
+ spropSize);
+ h264Config->sprop_parameter_sets[spropSize - 1] = '\0';
+ h264Config->packetization_mode = desc.mPacketizationMode;
+ h264Config->profile_level_id = desc.mProfileLevelId;
+ h264Config->tias_bw = 0; // TODO(bug 1403206)
+ }
+
+ aConfig->reset(new VideoCodecConfig(pt, desc.mName, desc.mConstraints,
+ h264Config.get()));
+
+ (*aConfig)->mAckFbTypes = desc.mAckFbTypes;
+ (*aConfig)->mNackFbTypes = desc.mNackFbTypes;
+ (*aConfig)->mCcmFbTypes = desc.mCcmFbTypes;
+ (*aConfig)->mRembFbSet = desc.RtcpFbRembIsSet();
+ (*aConfig)->mFECFbSet = desc.mFECEnabled;
+ (*aConfig)->mTransportCCFbSet = desc.RtcpFbTransportCCIsSet();
+ if (desc.mFECEnabled) {
+ uint16_t pt;
+ if (SdpHelper::GetPtAsInt(desc.mREDPayloadType, &pt)) {
+ (*aConfig)->mREDPayloadType = pt;
+ }
+ if (SdpHelper::GetPtAsInt(desc.mULPFECPayloadType, &pt)) {
+ (*aConfig)->mULPFECPayloadType = pt;
+ }
+ }
+ if (desc.mRtxEnabled) {
+ uint16_t pt;
+ if (SdpHelper::GetPtAsInt(desc.mRtxPayloadType, &pt)) {
+ (*aConfig)->mRTXPayloadType = pt;
+ }
+ }
+
+ return NS_OK;
+}
+
+// TODO: Maybe move this someplace else?
+/*static*/
+nsresult TransceiverImpl::NegotiatedDetailsToVideoCodecConfigs(
+ const JsepTrackNegotiatedDetails& aDetails,
+ std::vector<UniquePtr<VideoCodecConfig>>* aConfigs) {
+ if (aDetails.GetEncodingCount()) {
+ for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) {
+ UniquePtr<VideoCodecConfig> config;
+ if (NS_FAILED(JsepCodecDescToVideoCodecConfig(*codec, &config))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ config->mTias = aDetails.GetTias();
+
+ for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) {
+ const JsepTrackEncoding& jsepEncoding(aDetails.GetEncoding(i));
+ if (jsepEncoding.HasFormat(codec->mDefaultPt)) {
+ VideoCodecConfig::Encoding encoding;
+ encoding.rid = jsepEncoding.mRid;
+ encoding.constraints = jsepEncoding.mConstraints;
+ config->mEncodings.push_back(encoding);
+ }
+ }
+
+ aConfigs->push_back(std::move(config));
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult TransceiverImpl::UpdateVideoConduit() {
+ MOZ_ASSERT(IsValid());
+
+ RefPtr<VideoSessionConduit> conduit =
+ static_cast<VideoSessionConduit*>(mConduit.get());
+
+ // It is possible for SDP to signal that there is a send track, but there not
+ // actually be a send track, according to the specification; all that needs to
+ // happen is for the transceiver to be configured to send...
+ if (mJsepTransceiver->mSendTrack.GetNegotiatedDetails() &&
+ mJsepTransceiver->mSendTrack.GetActive() && mSendTrack) {
+ const auto& details(*mJsepTransceiver->mSendTrack.GetNegotiatedDetails());
+
+ UpdateConduitRtpExtmap(*conduit, details, LocalDirection::kSend);
+
+ nsresult rv = ConfigureVideoCodecMode(*conduit);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ std::vector<UniquePtr<VideoCodecConfig>> configs;
+ rv = TransceiverImpl::NegotiatedDetailsToVideoCodecConfigs(details,
+ &configs);
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle
+ << "[" << mMid << "]: " << __FUNCTION__
+ << " Failed to convert JsepCodecDescriptions to "
+ "VideoCodecConfigs (send).");
+ return rv;
+ }
+
+ if (configs.empty()) {
+ MOZ_MTLOG(ML_INFO, mPCHandle << "[" << mMid << "]: " << __FUNCTION__
+ << " No codecs were negotiated (send).");
+ return NS_OK;
+ }
+
+ auto error = conduit->ConfigureSendMediaCodec(configs[0].get(),
+ details.GetRtpRtcpConfig());
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle
+ << "[" << mMid << "]: " << __FUNCTION__
+ << " ConfigureSendMediaCodec failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult TransceiverImpl::ConfigureVideoCodecMode(
+ VideoSessionConduit& aConduit) {
+ RefPtr<mozilla::dom::VideoStreamTrack> videotrack =
+ mSendTrack->AsVideoStreamTrack();
+
+ if (!videotrack) {
+ MOZ_MTLOG(
+ ML_ERROR, mPCHandle
+ << "[" << mMid << "]: " << __FUNCTION__
+ << " mSendTrack is not video! This should never happen!");
+ MOZ_CRASH();
+ return NS_ERROR_FAILURE;
+ }
+
+ dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource();
+ webrtc::VideoCodecMode mode = webrtc::kRealtimeVideo;
+ switch (source) {
+ case dom::MediaSourceEnum::Browser:
+ case dom::MediaSourceEnum::Screen:
+ case dom::MediaSourceEnum::Window:
+ mode = webrtc::kScreensharing;
+ break;
+
+ case dom::MediaSourceEnum::Camera:
+ default:
+ mode = webrtc::kRealtimeVideo;
+ break;
+ }
+
+ auto error = aConduit.ConfigureCodecMode(mode);
+ if (error) {
+ MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__
+ << " ConfigureCodecMode failed: " << error);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void TransceiverImpl::UpdateConduitRtpExtmap(
+ MediaSessionConduit& aConduit, const JsepTrackNegotiatedDetails& aDetails,
+ const LocalDirection aDirection) {
+ std::vector<webrtc::RtpExtension> extmaps;
+ // @@NG read extmap from track
+ aDetails.ForEachRTPHeaderExtension(
+ [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) {
+ extmaps.emplace_back(extmap.extensionname, extmap.entry);
+ });
+ if (!extmaps.empty()) {
+ aConduit.SetLocalRTPExtensions(aDirection, extmaps);
+ }
+}
+
+void TransceiverImpl::Stop() {
+ mTransmitPipeline->Shutdown_m();
+ mReceiver->Shutdown();
+ // Make sure that stats queries stop working on this transceiver.
+ UpdateSendTrack(nullptr);
+
+ if (mConduit) {
+ mConduit->DeleteStreams();
+ }
+ mConduit = nullptr;
+
+ if (mDtmf) {
+ mDtmf->StopPlayout();
+ }
+}
+
+bool TransceiverImpl::IsVideo() const {
+ return mJsepTransceiver->GetMediaType() == SdpMediaSection::MediaType::kVideo;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/TransceiverImpl.h b/dom/media/webrtc/jsapi/TransceiverImpl.h
new file mode 100644
index 0000000000..335faa7579
--- /dev/null
+++ b/dom/media/webrtc/jsapi/TransceiverImpl.h
@@ -0,0 +1,185 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+#ifndef _TRANSCEIVERIMPL_H_
+#define _TRANSCEIVERIMPL_H_
+
+#include <string>
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsISerialEventTarget.h"
+#include "nsTArray.h"
+#include "mozilla/dom/MediaStreamTrack.h"
+#include "ErrorList.h"
+#include "jsep/JsepTransceiver.h"
+#include "transport/transportlayer.h" // For TransportLayer::State
+
+class nsIPrincipal;
+
+namespace mozilla {
+class PeerIdentity;
+class JsepTransceiver;
+enum class MediaSessionConduitLocalDirection : int;
+class MediaSessionConduit;
+class VideoSessionConduit;
+class AudioSessionConduit;
+struct AudioCodecConfig;
+class VideoCodecConfig; // Why is this a class, but AudioCodecConfig a struct?
+class MediaPipelineTransmit;
+class MediaPipeline;
+class MediaPipelineFilter;
+class MediaTransportHandler;
+class WebRtcCallWrapper;
+class JsepTrackNegotiatedDetails;
+
+namespace dom {
+class RTCDtlsTransport;
+class RTCDTMFSender;
+class RTCRtpTransceiver;
+struct RTCRtpSourceEntry;
+class RTCRtpReceiver;
+} // namespace dom
+
+/**
+ * This is what ties all the various pieces that make up a transceiver
+ * together. This includes:
+ * MediaStreamTrack for rendering and capture
+ * MediaTransportHandler for RTP transmission/reception
+ * Audio/VideoConduit for feeding RTP/RTCP into webrtc.org for decoding, and
+ * feeding audio/video frames into webrtc.org for encoding into RTP/RTCP.
+ */
+class TransceiverImpl : public nsISupports,
+ public nsWrapperCache,
+ public sigslot::has_slots<> {
+ public:
+ /**
+ * |aSendTrack| might or might not be set.
+ */
+ TransceiverImpl(nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded,
+ const std::string& aPCHandle,
+ MediaTransportHandler* aTransportHandler,
+ JsepTransceiver* aJsepTransceiver,
+ nsISerialEventTarget* aMainThread,
+ nsISerialEventTarget* aStsThread,
+ dom::MediaStreamTrack* aSendTrack,
+ WebRtcCallWrapper* aCallWrapper);
+
+ bool IsValid() const { return !!mConduit; }
+
+ nsresult UpdateSendTrack(dom::MediaStreamTrack* aSendTrack);
+
+ nsresult UpdateSinkIdentity(const dom::MediaStreamTrack* aTrack,
+ nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity);
+
+ nsresult UpdateTransport();
+
+ nsresult UpdateConduit();
+
+ void ResetSync();
+
+ nsresult SyncWithMatchingVideoConduits(
+ std::vector<RefPtr<TransceiverImpl>>& transceivers);
+
+ void Shutdown_m();
+
+ bool ConduitHasPluginID(uint64_t aPluginID);
+
+ bool HasSendTrack(const dom::MediaStreamTrack* aSendTrack) const;
+
+ // This is so PCImpl can unregister from PrincipalChanged callbacks; maybe we
+ // should have TransceiverImpl handle these callbacks instead? It would need
+ // to be able to get a ref to PCImpl though.
+ RefPtr<dom::MediaStreamTrack> GetSendTrack() { return mSendTrack; }
+
+ // for webidl
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsPIDOMWindowInner* GetParentObject() const;
+ void SyncWithJS(dom::RTCRtpTransceiver& aJsTransceiver, ErrorResult& aRv);
+ dom::RTCRtpReceiver* Receiver() const { return mReceiver; }
+ dom::RTCDTMFSender* GetDtmf() const { return mDtmf; }
+ dom::RTCDtlsTransport* GetDtlsTransport() const { return mDtlsTransport; }
+
+ bool CanSendDTMF() const;
+
+ // TODO: These are for stats; try to find a cleaner way.
+ RefPtr<MediaPipelineTransmit> GetSendPipeline();
+
+ void UpdateDtlsTransportState(const std::string& aTransportId,
+ TransportLayer::State aState);
+ void SetDtlsTransport(dom::RTCDtlsTransport* aDtlsTransport, bool aStable);
+ void RollbackToStableDtlsTransport();
+
+ std::string GetTransportId() const {
+ return mJsepTransceiver->mTransport.mTransportId;
+ }
+
+ bool IsVideo() const;
+
+ bool IsSending() const {
+ return !mJsepTransceiver->IsStopped() &&
+ mJsepTransceiver->mSendTrack.GetActive();
+ }
+
+ bool IsReceiving() const {
+ return !mJsepTransceiver->IsStopped() &&
+ mJsepTransceiver->mRecvTrack.GetActive();
+ }
+
+ MediaSessionConduit* GetConduit() const { return mConduit; }
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TransceiverImpl)
+
+ static nsresult NegotiatedDetailsToAudioCodecConfigs(
+ const JsepTrackNegotiatedDetails& aDetails,
+ std::vector<UniquePtr<AudioCodecConfig>>* aConfigs);
+
+ static nsresult NegotiatedDetailsToVideoCodecConfigs(
+ const JsepTrackNegotiatedDetails& aDetails,
+ std::vector<UniquePtr<VideoCodecConfig>>* aConfigs);
+
+ static void UpdateConduitRtpExtmap(
+ MediaSessionConduit& aConduit, const JsepTrackNegotiatedDetails& aDetails,
+ const MediaSessionConduitLocalDirection aDir);
+
+ private:
+ virtual ~TransceiverImpl();
+ void InitAudio();
+ void InitVideo();
+ nsresult UpdateAudioConduit();
+ nsresult UpdateVideoConduit();
+ nsresult ConfigureVideoCodecMode(VideoSessionConduit& aConduit);
+ void Stop();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ const std::string mPCHandle;
+ RefPtr<MediaTransportHandler> mTransportHandler;
+ RefPtr<JsepTransceiver> mJsepTransceiver;
+ std::string mMid;
+ bool mHaveSetupTransport;
+ nsCOMPtr<nsISerialEventTarget> mMainThread;
+ nsCOMPtr<nsISerialEventTarget> mStsThread;
+ RefPtr<dom::MediaStreamTrack> mSendTrack;
+ // state for webrtc.org that is shared between all transceivers
+ RefPtr<WebRtcCallWrapper> mCallWrapper;
+ RefPtr<MediaSessionConduit> mConduit;
+ RefPtr<MediaPipelineTransmit> mTransmitPipeline;
+ // The spec says both RTCRtpReceiver and RTCRtpSender have a slot for
+ // an RTCDtlsTransport. They are always the same, so we'll store it
+ // here.
+ RefPtr<dom::RTCDtlsTransport> mDtlsTransport;
+ // The spec says both RTCRtpReceiver and RTCRtpSender have a slot for
+ // a last stable state RTCDtlsTransport. They are always the same, so
+ // we'll store it here.
+ RefPtr<dom::RTCDtlsTransport> mLastStableDtlsTransport;
+ RefPtr<dom::RTCRtpReceiver> mReceiver;
+ // TODO(bug 1616937): Move this to RTCRtpSender
+ RefPtr<dom::RTCDTMFSender> mDtmf;
+};
+
+} // namespace mozilla
+
+#endif // _TRANSCEIVERIMPL_H_
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalChild.h b/dom/media/webrtc/jsapi/WebrtcGlobalChild.h
new file mode 100644
index 0000000000..cb0ecfd041
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalChild.h
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _WEBRTC_GLOBAL_CHILD_H_
+#define _WEBRTC_GLOBAL_CHILD_H_
+
+#include "mozilla/dom/PWebrtcGlobalChild.h"
+
+namespace mozilla {
+namespace dom {
+
+class WebrtcGlobalChild : public PWebrtcGlobalChild {
+ friend class ContentChild;
+
+ bool mShutdown;
+
+ MOZ_IMPLICIT WebrtcGlobalChild();
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual mozilla::ipc::IPCResult RecvGetStats(
+ const nsString& aPcIdFilter, GetStatsResolver&& aResolve) override;
+ virtual mozilla::ipc::IPCResult RecvClearStats() override;
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY because we can't do MOZ_CAN_RUN_SCRIPT in
+ // ipdl-generated things yet.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual mozilla::ipc::IPCResult RecvGetLog(
+ GetLogResolver&& aResolve) override;
+ virtual mozilla::ipc::IPCResult RecvClearLog() override;
+ virtual mozilla::ipc::IPCResult RecvSetAecLogging(
+ const bool& aEnable) override;
+ virtual mozilla::ipc::IPCResult RecvSetDebugMode(const int& aLevel) override;
+
+ public:
+ virtual ~WebrtcGlobalChild();
+ static WebrtcGlobalChild* Create();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _WEBRTC_GLOBAL_CHILD_H_
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp
new file mode 100644
index 0000000000..25bcc2a6b0
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp
@@ -0,0 +1,714 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "WebrtcGlobalInformation.h"
+#include "mozilla/media/webrtc/WebrtcGlobal.h"
+#include "WebrtcGlobalChild.h"
+#include "WebrtcGlobalParent.h"
+
+#include <algorithm>
+#include <vector>
+#include <type_traits>
+
+#include "mozilla/dom/WebrtcGlobalInformationBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h" // for RTCStatsReportInternal
+#include "mozilla/dom/ContentChild.h"
+
+#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID
+#include "nsServiceManagerUtils.h" // do_GetService
+#include "mozilla/ErrorResult.h"
+#include "nsProxyRelease.h" // nsMainThreadPtrHolder
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+
+#include "common/browser_logging/WebRtcLog.h"
+#include "transport/runnable_utils.h"
+#include "MediaTransportHandler.h"
+#include "PeerConnectionCtx.h"
+#include "PeerConnectionImpl.h"
+
+namespace mozilla::dom {
+
+typedef nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback>
+ StatsRequestCallback;
+
+typedef nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback> LogRequestCallback;
+
+class WebrtcContentParents {
+ public:
+ static WebrtcGlobalParent* Alloc();
+ static void Dealloc(WebrtcGlobalParent* aParent);
+ static bool Empty() { return sContentParents.empty(); }
+ static const std::vector<RefPtr<WebrtcGlobalParent>>& GetAll() {
+ return sContentParents;
+ }
+
+ private:
+ static std::vector<RefPtr<WebrtcGlobalParent>> sContentParents;
+ WebrtcContentParents() = delete;
+ WebrtcContentParents(const WebrtcContentParents&) = delete;
+ WebrtcContentParents& operator=(const WebrtcContentParents&) = delete;
+};
+
+std::vector<RefPtr<WebrtcGlobalParent>> WebrtcContentParents::sContentParents;
+
+WebrtcGlobalParent* WebrtcContentParents::Alloc() {
+ RefPtr<WebrtcGlobalParent> cp = new WebrtcGlobalParent;
+ sContentParents.push_back(cp);
+ return cp.get();
+}
+
+void WebrtcContentParents::Dealloc(WebrtcGlobalParent* aParent) {
+ if (aParent) {
+ aParent->mShutdown = true;
+ auto cp =
+ std::find(sContentParents.begin(), sContentParents.end(), aParent);
+ if (cp != sContentParents.end()) {
+ sContentParents.erase(cp);
+ }
+ }
+}
+
+static PeerConnectionCtx* GetPeerConnectionCtx() {
+ if (PeerConnectionCtx::isActive()) {
+ MOZ_ASSERT(PeerConnectionCtx::GetInstance());
+ return PeerConnectionCtx::GetInstance();
+ }
+ return nullptr;
+}
+
+static RefPtr<PWebrtcGlobalParent::GetStatsPromise>
+GetStatsPromiseForThisProcess(const nsAString& aPcIdFilter) {
+ nsTArray<RefPtr<dom::RTCStatsReportPromise>> promises;
+
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ if (ctx) {
+ // Grab stats for non-closed PCs
+ for (const auto& [id, pc] : ctx->GetPeerConnections()) {
+ if (aPcIdFilter.IsEmpty() || aPcIdFilter.EqualsASCII(id.c_str())) {
+ if (pc->HasMedia()) {
+ promises.AppendElement(pc->GetStats(nullptr, true));
+ }
+ }
+ }
+
+ // Grab stats for closed PCs
+ for (const auto& report : ctx->mStatsForClosedPeerConnections) {
+ if (aPcIdFilter.IsEmpty() || aPcIdFilter == report.mPcid) {
+ promises.AppendElement(dom::RTCStatsReportPromise::CreateAndResolve(
+ MakeUnique<dom::RTCStatsReportInternal>(report), __func__));
+ }
+ }
+ }
+
+ auto UnwrapUniquePtrs = [](dom::RTCStatsReportPromise::AllSettledPromiseType::
+ ResolveOrRejectValue&& aResult) {
+ nsTArray<dom::RTCStatsReportInternal> reports;
+ MOZ_RELEASE_ASSERT(aResult.IsResolve(), "AllSettled should never reject!");
+ for (auto& reportResult : aResult.ResolveValue()) {
+ if (reportResult.IsResolve()) {
+ reports.AppendElement(*reportResult.ResolveValue());
+ }
+ }
+ return PWebrtcGlobalParent::GetStatsPromise::CreateAndResolve(
+ std::move(reports), __func__);
+ };
+
+ return dom::RTCStatsReportPromise::AllSettled(
+ GetMainThreadSerialEventTarget(), promises)
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ std::move(UnwrapUniquePtrs));
+}
+
+static nsTArray<dom::RTCStatsReportInternal>& GetWebrtcGlobalStatsStash() {
+ static StaticAutoPtr<nsTArray<dom::RTCStatsReportInternal>> sStash;
+ if (!sStash) {
+ sStash = new nsTArray<dom::RTCStatsReportInternal>();
+ ClearOnShutdown(&sStash);
+ }
+ return *sStash;
+}
+
+static std::map<int32_t, dom::Sequence<nsString>>& GetWebrtcGlobalLogStash() {
+ static StaticAutoPtr<std::map<int32_t, dom::Sequence<nsString>>> sStash;
+ if (!sStash) {
+ sStash = new std::map<int32_t, dom::Sequence<nsString>>();
+ ClearOnShutdown(&sStash);
+ }
+ return *sStash;
+}
+
+static void ClearClosedStats() {
+ GetWebrtcGlobalStatsStash().Clear();
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+
+ if (ctx) {
+ ctx->mStatsForClosedPeerConnections.Clear();
+ }
+}
+
+void WebrtcGlobalInformation::ClearAllStats(const GlobalObject& aGlobal) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ // Chrome-only API
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!WebrtcContentParents::Empty()) {
+ // Pass on the request to any content process based PeerConnections.
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendClearStats();
+ }
+ }
+
+ // Flush the history for the chrome process
+ ClearClosedStats();
+}
+
+void WebrtcGlobalInformation::GetAllStats(
+ const GlobalObject& aGlobal, WebrtcGlobalStatisticsCallback& aStatsCallback,
+ const Optional<nsAString>& pcIdFilter, ErrorResult& aRv) {
+ if (!NS_IsMainThread()) {
+ aRv.Throw(NS_ERROR_NOT_SAME_THREAD);
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsTArray<RefPtr<PWebrtcGlobalParent::GetStatsPromise>> statsPromises;
+
+ nsString filter;
+ if (pcIdFilter.WasPassed()) {
+ filter = pcIdFilter.Value();
+ }
+
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ statsPromises.AppendElement(cp->SendGetStats(filter));
+ }
+
+ // Stats from this (the parent) process. How long do we keep supporting this?
+ statsPromises.AppendElement(GetStatsPromiseForThisProcess(filter));
+
+ // CallbackObject does not support threadsafe refcounting, and must be
+ // used and destroyed on main.
+ StatsRequestCallback callbackHandle(
+ new nsMainThreadPtrHolder<WebrtcGlobalStatisticsCallback>(
+ "WebrtcGlobalStatisticsCallback", &aStatsCallback));
+
+ auto FlattenThenStashThenCallback =
+ [callbackHandle,
+ filter](PWebrtcGlobalParent::GetStatsPromise::AllSettledPromiseType::
+ ResolveOrRejectValue&& aResult) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ std::set<nsString> pcids;
+ WebrtcGlobalStatisticsReport flattened;
+ MOZ_RELEASE_ASSERT(aResult.IsResolve(),
+ "AllSettled should never reject!");
+ for (auto& contentProcessResult : aResult.ResolveValue()) {
+ // TODO: Report rejection on individual content processes someday?
+ if (contentProcessResult.IsResolve()) {
+ for (auto& pcStats : contentProcessResult.ResolveValue()) {
+ pcids.insert(pcStats.mPcid);
+ if (!flattened.mReports.AppendElement(std::move(pcStats),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ }
+
+ if (filter.IsEmpty()) {
+ // Unfiltered is pretty simple; add stuff from stash that is
+ // missing, then stomp the stash with the new reports.
+ for (auto& pcStats : GetWebrtcGlobalStatsStash()) {
+ if (!pcids.count(pcStats.mPcid)) {
+ // Stats from a closed PC or stopped content process.
+ // Content process may have gone away before we got to update
+ // this.
+ pcStats.mClosed = true;
+ if (!flattened.mReports.AppendElement(std::move(pcStats),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ GetWebrtcGlobalStatsStash() = flattened.mReports;
+ } else {
+ // Filtered is slightly more complex
+ if (flattened.mReports.IsEmpty()) {
+ // Find entry from stash and add it to report
+ for (auto& pcStats : GetWebrtcGlobalStatsStash()) {
+ if (pcStats.mPcid == filter) {
+ pcStats.mClosed = true;
+ if (!flattened.mReports.AppendElement(std::move(pcStats),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ } else {
+ // Find entries in stash, remove them, and then add new entries
+ for (size_t i = 0; i < GetWebrtcGlobalStatsStash().Length();) {
+ auto& pcStats = GetWebrtcGlobalStatsStash()[i];
+ if (pcStats.mPcid == filter) {
+ GetWebrtcGlobalStatsStash().RemoveElementAt(i);
+ } else {
+ ++i;
+ }
+ }
+ GetWebrtcGlobalStatsStash().AppendElements(flattened.mReports);
+ }
+ }
+
+ IgnoredErrorResult rv;
+ callbackHandle->Call(flattened, rv);
+ };
+
+ PWebrtcGlobalParent::GetStatsPromise::AllSettled(
+ GetMainThreadSerialEventTarget(), statsPromises)
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ std::move(FlattenThenStashThenCallback));
+
+ aRv = NS_OK;
+}
+
+static RefPtr<PWebrtcGlobalParent::GetLogPromise> GetLogPromise() {
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ if (!ctx) {
+ // This process has never created a PeerConnection, so no ICE logging.
+ return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve(
+ Sequence<nsString>(), __func__);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISerialEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_WARN_IF(NS_FAILED(rv) || !stsThread)) {
+ return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve(
+ Sequence<nsString>(), __func__);
+ }
+
+ RefPtr<MediaTransportHandler> transportHandler = ctx->GetTransportHandler();
+
+ auto AddMarkers =
+ [](MediaTransportHandler::IceLogPromise::ResolveOrRejectValue&& aValue) {
+ nsString pid;
+ pid.AppendInt(getpid());
+ Sequence<nsString> logs;
+ if (aValue.IsResolve() && !aValue.ResolveValue().IsEmpty()) {
+ bool ok = logs.AppendElement(
+ u"+++++++ BEGIN (process id "_ns + pid + u") ++++++++"_ns,
+ fallible);
+ ok &=
+ !!logs.AppendElements(std::move(aValue.ResolveValue()), fallible);
+ ok &= !!logs.AppendElement(
+ u"+++++++ END (process id "_ns + pid + u") ++++++++"_ns,
+ fallible);
+ if (!ok) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve(
+ std::move(logs), __func__);
+ };
+
+ return transportHandler->GetIceLog(nsCString())
+ ->Then(GetMainThreadSerialEventTarget(), __func__, std::move(AddMarkers));
+}
+
+static nsresult RunLogClear() {
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ if (!ctx) {
+ // This process has never created a PeerConnection, so no ICE logging.
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISerialEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!stsThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<MediaTransportHandler> transportHandler = ctx->GetTransportHandler();
+
+ return RUN_ON_THREAD(
+ stsThread,
+ WrapRunnable(transportHandler, &MediaTransportHandler::ClearIceLog),
+ NS_DISPATCH_NORMAL);
+}
+
+void WebrtcGlobalInformation::ClearLogging(const GlobalObject& aGlobal) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ // Chrome-only API
+ MOZ_ASSERT(XRE_IsParentProcess());
+ GetWebrtcGlobalLogStash().clear();
+
+ if (!WebrtcContentParents::Empty()) {
+ // Clear content process signaling logs
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendClearLog();
+ }
+ }
+
+ // Clear chrome process signaling logs
+ Unused << RunLogClear();
+}
+
+static RefPtr<GenericPromise> UpdateLogStash() {
+ nsTArray<RefPtr<GenericPromise>> logPromises;
+ MOZ_ASSERT(XRE_IsParentProcess());
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ auto StashLog =
+ [id = cp->Id() * 2 /* Make sure 1 isn't used */](
+ PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve() && !aValue.ResolveValue().IsEmpty()) {
+ GetWebrtcGlobalLogStash()[id] = aValue.ResolveValue();
+ }
+ return GenericPromise::CreateAndResolve(true, __func__);
+ };
+ logPromises.AppendElement(cp->SendGetLog()->Then(
+ GetMainThreadSerialEventTarget(), __func__, std::move(StashLog)));
+ }
+
+ // Get ICE logging for this (the parent) process. How long do we support this?
+ logPromises.AppendElement(GetLogPromise()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [](PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve()) {
+ GetWebrtcGlobalLogStash()[1] = aValue.ResolveValue();
+ }
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }));
+
+ return GenericPromise::AllSettled(GetMainThreadSerialEventTarget(),
+ logPromises)
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ [](GenericPromise::AllSettledPromiseType::ResolveOrRejectValue&&
+ aValue) {
+ // We don't care about the value, since we're just going to copy
+ // what is in the stash. This ignores failures too, which is what
+ // we want.
+ return GenericPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+void WebrtcGlobalInformation::GetLogging(
+ const GlobalObject& aGlobal, const nsAString& aPattern,
+ WebrtcGlobalLoggingCallback& aLoggingCallback, ErrorResult& aRv) {
+ if (!NS_IsMainThread()) {
+ aRv.Throw(NS_ERROR_NOT_SAME_THREAD);
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsAutoCString pattern;
+ CopyUTF16toUTF8(aPattern, pattern);
+
+ // CallbackObject does not support threadsafe refcounting, and must be
+ // destroyed on main.
+ LogRequestCallback callbackHandle(
+ new nsMainThreadPtrHolder<WebrtcGlobalLoggingCallback>(
+ "WebrtcGlobalLoggingCallback", &aLoggingCallback));
+
+ auto FilterThenCallback =
+ [pattern, callbackHandle](GenericPromise::ResolveOrRejectValue&& aValue)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ dom::Sequence<nsString> flattened;
+ for (const auto& [id, log] : GetWebrtcGlobalLogStash()) {
+ (void)id;
+ for (const auto& line : log) {
+ if (pattern.IsEmpty() || (line.Find(pattern) != kNotFound)) {
+ if (!flattened.AppendElement(line, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ }
+ IgnoredErrorResult rv;
+ callbackHandle->Call(flattened, rv);
+ };
+
+ UpdateLogStash()->Then(GetMainThreadSerialEventTarget(), __func__,
+ std::move(FilterThenCallback));
+ aRv = NS_OK;
+}
+
+static int32_t sLastSetLevel = 0;
+static bool sLastAECDebug = false;
+static Maybe<nsCString> sAecDebugLogDir;
+
+void WebrtcGlobalInformation::SetDebugLevel(const GlobalObject& aGlobal,
+ int32_t aLevel) {
+ if (aLevel) {
+ StartWebRtcLog(mozilla::LogLevel(aLevel));
+ } else {
+ StopWebRtcLog();
+ }
+ sLastSetLevel = aLevel;
+
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendSetDebugMode(aLevel);
+ }
+}
+
+int32_t WebrtcGlobalInformation::DebugLevel(const GlobalObject& aGlobal) {
+ return sLastSetLevel;
+}
+
+void WebrtcGlobalInformation::SetAecDebug(const GlobalObject& aGlobal,
+ bool aEnable) {
+ if (aEnable) {
+ sAecDebugLogDir = Some(StartAecLog());
+ } else {
+ StopAecLog();
+ }
+
+ sLastAECDebug = aEnable;
+
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendSetAecLogging(aEnable);
+ }
+}
+
+bool WebrtcGlobalInformation::AecDebug(const GlobalObject& aGlobal) {
+ return sLastAECDebug;
+}
+
+void WebrtcGlobalInformation::GetAecDebugLogDir(const GlobalObject& aGlobal,
+ nsAString& aDir) {
+ aDir = NS_ConvertASCIItoUTF16(sAecDebugLogDir.valueOr(""_ns));
+}
+
+WebrtcGlobalParent* WebrtcGlobalParent::Alloc() {
+ return WebrtcContentParents::Alloc();
+}
+
+bool WebrtcGlobalParent::Dealloc(WebrtcGlobalParent* aActor) {
+ WebrtcContentParents::Dealloc(aActor);
+ return true;
+}
+
+void WebrtcGlobalParent::ActorDestroy(ActorDestroyReason aWhy) {
+ mShutdown = true;
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalParent::Recv__delete__() {
+ return IPC_OK();
+}
+
+MOZ_IMPLICIT WebrtcGlobalParent::WebrtcGlobalParent() : mShutdown(false) {
+ MOZ_COUNT_CTOR(WebrtcGlobalParent);
+}
+
+MOZ_IMPLICIT WebrtcGlobalParent::~WebrtcGlobalParent() {
+ MOZ_COUNT_DTOR(WebrtcGlobalParent);
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvGetStats(
+ const nsString& aPcIdFilter, GetStatsResolver&& aResolve) {
+ if (!mShutdown) {
+ GetStatsPromiseForThisProcess(aPcIdFilter)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [resolve = std::move(aResolve)](
+ nsTArray<dom::RTCStatsReportInternal>&& aReports) {
+ resolve(std::move(aReports));
+ },
+ []() { MOZ_CRASH(); });
+ return IPC_OK();
+ }
+
+ aResolve(nsTArray<RTCStatsReportInternal>());
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvClearStats() {
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ ClearClosedStats();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvGetLog(
+ GetLogResolver&& aResolve) {
+ if (mShutdown) {
+ aResolve(Sequence<nsString>());
+ return IPC_OK();
+ }
+
+ GetLogPromise()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aResolve = std::move(aResolve)](
+ PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve()) {
+ aResolve(aValue.ResolveValue());
+ } else {
+ aResolve(Sequence<nsString>());
+ }
+ });
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvClearLog() {
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ RunLogClear();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvSetAecLogging(
+ const bool& aEnable) {
+ if (!mShutdown) {
+ if (aEnable) {
+ StartAecLog();
+ } else {
+ StopAecLog();
+ }
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvSetDebugMode(const int& aLevel) {
+ if (!mShutdown) {
+ if (aLevel) {
+ StartWebRtcLog(mozilla::LogLevel(aLevel));
+ } else {
+ StopWebRtcLog();
+ }
+ }
+ return IPC_OK();
+}
+
+WebrtcGlobalChild* WebrtcGlobalChild::Create() {
+ WebrtcGlobalChild* child = static_cast<WebrtcGlobalChild*>(
+ ContentChild::GetSingleton()->SendPWebrtcGlobalConstructor());
+ return child;
+}
+
+void WebrtcGlobalChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mShutdown = true;
+}
+
+MOZ_IMPLICIT WebrtcGlobalChild::WebrtcGlobalChild() : mShutdown(false) {
+ MOZ_COUNT_CTOR(WebrtcGlobalChild);
+}
+
+MOZ_IMPLICIT WebrtcGlobalChild::~WebrtcGlobalChild() {
+ MOZ_COUNT_DTOR(WebrtcGlobalChild);
+}
+
+static void StoreLongTermICEStatisticsImpl_m(RTCStatsReportInternal* report) {
+ using namespace Telemetry;
+
+ report->mClosed = true;
+
+ for (const auto& outboundRtpStats : report->mOutboundRtpStreamStats) {
+ bool isVideo = (outboundRtpStats.mId.Value().Find("video") != -1);
+ if (!isVideo) {
+ continue;
+ }
+ if (outboundRtpStats.mBitrateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_AVG_PER_CALL_KBPS,
+ uint32_t(outboundRtpStats.mBitrateMean.Value() / 1000));
+ }
+ if (outboundRtpStats.mBitrateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_STD_DEV_PER_CALL_KBPS,
+ uint32_t(outboundRtpStats.mBitrateStdDev.Value() / 1000));
+ }
+ if (outboundRtpStats.mFramerateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_AVG_PER_CALL,
+ uint32_t(outboundRtpStats.mFramerateMean.Value()));
+ }
+ if (outboundRtpStats.mFramerateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_10X_STD_DEV_PER_CALL,
+ uint32_t(outboundRtpStats.mFramerateStdDev.Value() * 10));
+ }
+ if (outboundRtpStats.mDroppedFrames.WasPassed() &&
+ report->mCallDurationMs.WasPassed()) {
+ double mins = report->mCallDurationMs.Value() / (1000 * 60);
+ if (mins > 0) {
+ Accumulate(
+ WEBRTC_VIDEO_ENCODER_DROPPED_FRAMES_PER_CALL_FPM,
+ uint32_t(double(outboundRtpStats.mDroppedFrames.Value()) / mins));
+ }
+ }
+ }
+
+ for (const auto& inboundRtpStats : report->mInboundRtpStreamStats) {
+ bool isVideo = (inboundRtpStats.mId.Value().Find("video") != -1);
+ if (!isVideo) {
+ continue;
+ }
+ if (inboundRtpStats.mBitrateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_AVG_PER_CALL_KBPS,
+ uint32_t(inboundRtpStats.mBitrateMean.Value() / 1000));
+ }
+ if (inboundRtpStats.mBitrateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_STD_DEV_PER_CALL_KBPS,
+ uint32_t(inboundRtpStats.mBitrateStdDev.Value() / 1000));
+ }
+ if (inboundRtpStats.mFramerateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_AVG_PER_CALL,
+ uint32_t(inboundRtpStats.mFramerateMean.Value()));
+ }
+ if (inboundRtpStats.mFramerateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_10X_STD_DEV_PER_CALL,
+ uint32_t(inboundRtpStats.mFramerateStdDev.Value() * 10));
+ }
+ if (inboundRtpStats.mDiscardedPackets.WasPassed() &&
+ report->mCallDurationMs.WasPassed()) {
+ double mins = report->mCallDurationMs.Value() / (1000 * 60);
+ if (mins > 0) {
+ Accumulate(
+ WEBRTC_VIDEO_DECODER_DISCARDED_PACKETS_PER_CALL_PPM,
+ uint32_t(double(inboundRtpStats.mDiscardedPackets.Value()) / mins));
+ }
+ }
+ }
+
+ // Finally, store the stats
+
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ if (ctx) {
+ if (!ctx->mStatsForClosedPeerConnections.AppendElement(*report, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+}
+
+void WebrtcGlobalInformation::StoreLongTermICEStatistics(
+ PeerConnectionImpl& aPc) {
+ if (aPc.IceConnectionState() == RTCIceConnectionState::New) {
+ // ICE has not started; we won't have any remote candidates, so recording
+ // statistics on gathered candidates is pointless.
+ return;
+ }
+
+ aPc.GetStats(nullptr, true)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [=](UniquePtr<dom::RTCStatsReportInternal>&& aReport) {
+ StoreLongTermICEStatisticsImpl_m(aReport.get());
+ },
+ [=](nsresult aError) {});
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h
new file mode 100644
index 0000000000..081fcc1279
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _WEBRTC_GLOBAL_INFORMATION_H_
+#define _WEBRTC_GLOBAL_INFORMATION_H_
+
+#include "nsString.h"
+#include "mozilla/dom/BindingDeclarations.h" // for Optional
+
+namespace mozilla {
+class PeerConnectionImpl;
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class WebrtcGlobalStatisticsCallback;
+class WebrtcGlobalLoggingCallback;
+
+class WebrtcGlobalInformation {
+ public:
+ MOZ_CAN_RUN_SCRIPT
+ static void GetAllStats(const GlobalObject& aGlobal,
+ WebrtcGlobalStatisticsCallback& aStatsCallback,
+ const Optional<nsAString>& pcIdFilter,
+ ErrorResult& aRv);
+
+ static void ClearAllStats(const GlobalObject& aGlobal);
+
+ MOZ_CAN_RUN_SCRIPT
+ static void GetLogging(const GlobalObject& aGlobal, const nsAString& aPattern,
+ WebrtcGlobalLoggingCallback& aLoggingCallback,
+ ErrorResult& aRv);
+
+ static void ClearLogging(const GlobalObject& aGlobal);
+
+ static void SetDebugLevel(const GlobalObject& aGlobal, int32_t aLevel);
+ static int32_t DebugLevel(const GlobalObject& aGlobal);
+
+ static void SetAecDebug(const GlobalObject& aGlobal, bool aEnable);
+ static bool AecDebug(const GlobalObject& aGlobal);
+ static void GetAecDebugLogDir(const GlobalObject& aGlobal, nsAString& aDir);
+
+ static void StoreLongTermICEStatistics(PeerConnectionImpl& aPc);
+
+ private:
+ WebrtcGlobalInformation() = delete;
+ WebrtcGlobalInformation(const WebrtcGlobalInformation& aOrig) = delete;
+ WebrtcGlobalInformation& operator=(const WebrtcGlobalInformation& aRhs) =
+ delete;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _WEBRTC_GLOBAL_INFORMATION_H_
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalParent.h b/dom/media/webrtc/jsapi/WebrtcGlobalParent.h
new file mode 100644
index 0000000000..c7eebd7060
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalParent.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#ifndef _WEBRTC_GLOBAL_PARENT_H_
+#define _WEBRTC_GLOBAL_PARENT_H_
+
+#include "mozilla/dom/PWebrtcGlobalParent.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace dom {
+
+class WebrtcParents;
+
+class WebrtcGlobalParent : public PWebrtcGlobalParent {
+ friend class ContentParent;
+ friend class WebrtcGlobalInformation;
+ friend class WebrtcContentParents;
+
+ bool mShutdown;
+
+ MOZ_IMPLICIT WebrtcGlobalParent();
+
+ static WebrtcGlobalParent* Alloc();
+ static bool Dealloc(WebrtcGlobalParent* aActor);
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+ virtual mozilla::ipc::IPCResult Recv__delete__() override;
+
+ virtual ~WebrtcGlobalParent();
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WebrtcGlobalParent)
+
+ bool IsActive() { return !mShutdown; }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _WEBRTC_GLOBAL_PARENT_H_
diff --git a/dom/media/webrtc/jsapi/moz.build b/dom/media/webrtc/jsapi/moz.build
new file mode 100644
index 0000000000..978df17d29
--- /dev/null
+++ b/dom/media/webrtc/jsapi/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# 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("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
+
+LOCAL_INCLUDES += [
+ "!/ipc/ipdl/_ipdlheaders",
+ "/dom/base",
+ "/dom/media",
+ "/dom/media/webrtc",
+ "/ipc/chromium/src",
+ "/media/webrtc",
+ "/netwerk/srtp/src/include",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/webrtc",
+]
+
+UNIFIED_SOURCES += [
+ "MediaTransportHandler.cpp",
+ "MediaTransportHandlerIPC.cpp",
+ "MediaTransportParent.cpp",
+ "PacketDumper.cpp",
+ "PeerConnectionCtx.cpp",
+ "PeerConnectionImpl.cpp",
+ "PeerConnectionMedia.cpp",
+ "RTCDtlsTransport.cpp",
+ "RTCDTMFSender.cpp",
+ "RTCRtpReceiver.cpp",
+ "RTCStatsIdGenerator.cpp",
+ "RTCStatsReport.cpp",
+ "TransceiverImpl.cpp",
+ "WebrtcGlobalInformation.cpp",
+]
+
+FINAL_LIBRARY = "xul"