/* 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 "nsILoadContext.h"
#include "nsEffectiveTLDService.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 "libwebrtcglue/WebrtcCallWrapper.h"
#include "MediaTrackGraph.h"
#include "transport/runnable_utils.h"
#include "IPeerConnection.h"
#include "PeerConnectionCtx.h"
#include "PeerConnectionImpl.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 "transportbridge/RtpLogger.h"
#include "jsapi/RTCRtpReceiver.h"
#include "jsapi/RTCRtpSender.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/media/MediaUtils.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 "nsGlobalWindowInner.h"
#include "nsDOMDataChannel.h"
#include "mozilla/dom/Location.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/glean/DomMediaWebrtcMetrics.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/RTCSctpTransportBinding.h" // RTCSctpTransportState
#include "mozilla/dom/RTCDtlsTransportBinding.h" // RTCDtlsTransportState
#include "mozilla/dom/RTCIceTransportBinding.h" // RTCIceTransportState
#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"
#include "transport/nr_socket_proxy_config.h"
#include "RTCSctpTransport.h"
#include "RTCDtlsTransport.h"
#include "jsep/JsepTransport.h"
#include "nsILoadInfo.h"
#include "nsIPrincipal.h"
#include "mozilla/LoadInfo.h"
#include "nsIProxiedChannel.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/net/WebrtcProxyConfig.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(
bool aContainedAV) {
MOZ_ASSERT(mRefCnt);
mRefCnt--;
mUsedAV |= aContainedAV;
if (mRefCnt == 0) {
TimeDuration sample = TimeStamp::Now() - mStart;
if (mUsedAV) {
glean::webrtc::av_call_duration.AccumulateRawDuration(sample);
}
glean::webrtc::call_duration.AccumulateRawDuration(sample);
}
}
bool PeerConnectionAutoTimer::IsStopped() {
return mRefCnt == 0; }
// There is not presently an implementation of these for nsTHashMap :(
inline void ImplCycleCollectionUnlink(
PeerConnectionImpl::RTCDtlsTransportMap& aMap) {
for (
auto& tableEntry : aMap) {
ImplCycleCollectionUnlink(*tableEntry.GetModifiableData());
}
aMap.Clear();
}
inline void ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback,
PeerConnectionImpl::RTCDtlsTransportMap& aMap,
const char* aName,
uint32_t aFlags = 0) {
for (
auto& tableEntry : aMap) {
ImplCycleCollectionTraverse(aCallback, *tableEntry.GetModifiableData(),
aName, aFlags);
}
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(PeerConnectionImpl)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PeerConnectionImpl)
tmp->Close();
tmp->BreakCycles();
NS_IMPL_CYCLE_COLLECTION_UNLINK(
mPCObserver, mWindow, mCertificate, mSTSThread, mReceiveStreams,
mOperations, mTransportIdToRTCDtlsTransport, mSctpTransport,
mLastStableSctpTransport, mLastStableSctpDtlsTransport, mKungFuDeathGrip)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PeerConnectionImpl)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
mPCObserver, mWindow, mCertificate, mSTSThread, mReceiveStreams,
mOperations, mTransceivers, mTransportIdToRTCDtlsTransport,
mSctpTransport, mLastStableSctpTransport, mLastStableSctpDtlsTransport,
mKungFuDeathGrip)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PeerConnectionImpl)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
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();
}
JSObject* PeerConnectionImpl::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return PeerConnectionImpl_Binding::Wrap(aCx,
this, aGivenProto);
}
nsPIDOMWindowInner* PeerConnectionImpl::GetParentObject()
const {
return mWindow;
}
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),
mSignalingState(RTCSignalingState::Stable),
mIceConnectionState(RTCIceConnectionState::
New),
mIceGatheringState(RTCIceGatheringState::
New),
mConnectionState(RTCPeerConnectionState::
New),
mWindow(do_QueryInterface(aGlobal ? aGlobal->GetAsSupports() : nullptr)),
mCertificate(nullptr),
mSTSThread(nullptr),
mForceIceTcp(
false),
mTransportHandler(nullptr),
mUuidGen(MakeUnique<PCUuidGenerator>()),
mIceRestartCount(0),
mIceRollbackCount(0),
mHaveConfiguredCodecs(
false),
mTrickle(
true)
// TODO(ekr@rtfm.com): Use pref
,
mPrivateWindow(
false),
mActiveOnWindow(
false),
mTimestampMaker(dom::RTCStatsTimestampMaker::Create(mWindow)),
mIdGenerator(
new RTCStatsIdGenerator()),
listenPort(0),
connectPort(0),
connectStr(nullptr) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT_IF(aGlobal, mWindow);
mKungFuDeathGrip =
this;
if (aGlobal) {
if (IsPrivateBrowsing(mWindow)) {
mPrivateWindow =
true;
mDisableLongTermStats =
true;
}
mWindow->AddPeerConnection();
mActiveOnWindow =
true;
if (mWindow->GetDocumentURI()) {
mWindow->GetDocumentURI()->GetAsciiHost(mHostname);
nsresult rv;
nsCOMPtr<nsIEffectiveTLDService> eTLDService(
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
if (eTLDService) {
Unused << eTLDService->GetBaseDomain(mWindow->GetDocumentURI(), 0,
mEffectiveTLDPlus1);
}
mRtxIsAllowed = !media::HostnameInPref(
"media.peerconnection.video.use_rtx.blocklist", mHostname);
mDuplicateFingerprintQuirk = media::HostnameInPref(
"media.peerconnection.sdp.quirk.duplicate_fingerprint.allowlist",
mHostname);
}
}
if (!mUuidGen->Generate(&mHandle)) {
MOZ_CRASH();
}
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));
mJsConfiguration.mCertificatesProvided =
false;
mJsConfiguration.mPeerIdentityProvided =
false;
}
PeerConnectionImpl::~PeerConnectionImpl() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mTransportHandler,
"PeerConnection should either be closed, or not initted in the "
"first place.");
if (mTimeCard) {
STAMP_TIMECARD(mTimeCard,
"Destructor Invoked");
STAMP_TIMECARD(mTimeCard, mHandle.c_str());
print_timecard(mTimeCard);
destroy_timecard(mTimeCard);
mTimeCard = nullptr;
}
CSFLogInfo(LOGTAG,
"%s: PeerConnectionImpl destructor invoked for %s",
__FUNCTION__, mHandle.c_str());
}
nsresult PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
nsGlobalWindowInner* aWindow) {
nsresult res;
MOZ_ASSERT(NS_IsMainThread());
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, but we want it to be large enough to reliably
// contain the location on the tests we run in CI.
char temp[256];
nsAutoCString locationCStr;
RefPtr<Location> location = mWindow->Location();
res = location->GetHref(locationCStr);
NS_ENSURE_SUCCESS(res, res);
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();
NS_ENSURE_SUCCESS(res, res);
mTransportHandler->CreateIceCtx(
"PC:" + GetName());
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;
}
std::vector<UniquePtr<JsepCodecDescription>> preferredCodecs;
SetupPreferredCodecs(preferredCodecs);
mJsepSession->SetDefaultCodecs(preferredCodecs);
std::vector<RtpExtensionHeader> preferredHeaders;
SetupPreferredRtpExtensions(preferredHeaders);
for (
const auto& header : preferredHeaders) {
mJsepSession->AddRtpExtension(header.mMediaType, header.extensionname,
header.direction);
}
if (XRE_IsContentProcess()) {
mStunAddrsRequest =
new net::StunAddrsRequestChild(
new StunAddrsHandler(
this));
}
// Initialize the media object.
mForceProxy = ShouldForceProxy();
// We put this here, in case we later want to set this based on a non-standard
// param in RTCConfiguration.
mAllowOldSetParameters = Preferences::GetBool(
"media.peerconnection.allow_old_setParameters",
false);
// setup the stun local addresses IPC async call
InitLocalAddrs();
mSignalHandler = MakeUnique<SignalHandler>(
this, mTransportHandler.get());
PeerConnectionCtx::GetInstance()->AddPeerConnection(mHandle,
this);
return NS_OK;
}
void PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
nsGlobalWindowInner& aWindow,
ErrorResult& rv) {
MOZ_ASSERT(NS_IsMainThread());
nsresult res = Initialize(aObserver, &aWindow);
if (NS_FAILED(res)) {
rv.
Throw(res);
return;
}
}
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;
}
if (mUncommittedJsepSession) {
Unused << mUncommittedJsepSession->AddDtlsFingerprint(
DtlsIdentity::DEFAULT_HASH_ALGORITHM, fingerprint);
}
}
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(
const nsCString& preferredCodec) {
mPreferredCodec = preferredCodec;
}
bool operator()(
const UniquePtr<JsepCodecDescription>& lhs,
const UniquePtr<JsepCodecDescription>& rhs)
const {
// Do we have a preferred codec?
if (!mPreferredCodec.IsEmpty()) {
const bool lhsMatches = mPreferredCodec.EqualsIgnoreCase(lhs->mName) ||
mPreferredCodec.EqualsIgnoreCase(lhs->mDefaultPt);
const bool rhsMatches = mPreferredCodec.EqualsIgnoreCase(rhs->mName) ||
mPreferredCodec.EqualsIgnoreCase(rhs->mDefaultPt);
// If the only the left side matches, prefer it
if (lhsMatches && !rhsMatches) {
return true;
}
}
// If only the left side is strongly preferred, prefer it
return (lhs->mStronglyPreferred && !rhs->mStronglyPreferred);
}
private:
// The preferred codec name or PT number
nsCString mPreferredCodec;
};
class ConfigureCodec {
public:
explicit ConfigureCodec(nsCOMPtr<nsIPrefBranch>& branch)
: mHardwareH264Enabled(
false),
mSoftwareH264Enabled(
false),
mH264Enabled(
false),
mVP9Enabled(
true),
mVP9Preferred(
false),
mAV1Enabled(StaticPrefs::media_webrtc_codec_video_av1_enabled()),
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) {
mSoftwareH264Enabled = PeerConnectionCtx::GetInstance()->gmpHasH264();
if (WebrtcVideoConduit::HasH264Hardware()) {
glean::webrtc::has_h264_hardware
.EnumGet(glean::webrtc::HasH264HardwareLabel::eTrue)
.Add();
branch->GetBoolPref(
"media.webrtc.hw.h264.enabled",
&mHardwareH264Enabled);
}
mH264Enabled = mHardwareH264Enabled || mSoftwareH264Enabled;
glean::webrtc::software_h264_enabled
.EnumGet(
static_cast<glean::webrtc::SoftwareH264EnabledLabel>(
mSoftwareH264Enabled))
.Add();
glean::webrtc::hardware_h264_enabled
.EnumGet(
static_cast<glean::webrtc::HardwareH264EnabledLabel>(
mHardwareH264Enabled))
.Add();
glean::webrtc::h264_enabled
.EnumGet(
static_cast<glean::webrtc::H264EnabledLabel>(mH264Enabled))
.Add();
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);
}
void operator()(UniquePtr<JsepCodecDescription>& codec)
const {
switch (codec->Type()) {
case SdpMediaSection::kAudio: {
JsepAudioCodecDescription& audioCodec =
static_cast<JsepAudioCodecDescription&>(*codec);
if (audioCodec.mName ==
"opus") {
audioCodec.mFECEnabled = mUseAudioFec;
}
else if (audioCodec.mName ==
"telephone-event") {
audioCodec.mEnabled =
true;
}
}
break;
case SdpMediaSection::kVideo: {
JsepVideoCodecDescription& videoCodec =
static_cast<JsepVideoCodecDescription&>(*codec);
if (videoCodec.mName ==
"H264") {
// Override level but not for the pure Baseline codec
if (JsepVideoCodecDescription::GetSubprofile(
videoCodec.mProfileLevelId) ==
JsepVideoCodecDescription::kH264ConstrainedBaseline) {
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;
}
}
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 = Some(mVP8MaxFr);
}
else if (videoCodec.mName ==
"AV1") {
videoCodec.mEnabled = mAV1Enabled;
}
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;
bool mAV1Enabled;
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;
};
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);
// We use this to sort the list of codecs once everything is configured
CompareCodecPriority comparator;
if (StaticPrefs::media_webrtc_codec_video_av1_experimental_preferred()) {
comparator.SetPreferredCodec(nsCString(
"av1"));
}
// Sort by priority
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 = GetMainThreadSerialEventTarget();
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();
Maybe<
const JsepTransceiver> datachannelTransceiver =
mJsepSession->FindTransceiver([](
const JsepTransceiver& aTransceiver) {
return aTransceiver.GetMediaType() == SdpMediaSection::kApplication;
});
if (!datachannelTransceiver ||
!datachannelTransceiver->mTransport.mComponents ||
!datachannelTransceiver->mTransport.mDtls ||
!datachannelTransceiver->mSendTrack.GetNegotiatedDetails()) {
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->Type() != SdpMediaSection::kApplication) {
CSFLogError(LOGTAG,
"%s: Codec type for m=application was %u, this "
"is a bug.",
__FUNCTION__,
static_cast<
unsigned>(codec->Type()));
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(
JsepTransceiver& transceiver) {
nsresult res = ConfigureJsepSessionCodecs();
if (NS_FAILED(res)) {
CSFLogError(LOGTAG,
"Failed to configure codecs");
return res;
}
mJsepSession->AddTransceiver(transceiver);
return NS_OK;
}
static Maybe<SdpMediaSection::MediaType> ToSdpMediaType(
const nsAString& aKind) {
if (aKind.EqualsASCII(
"audio")) {
return Some(SdpMediaSection::MediaType::kAudio);
}
else if (aKind.EqualsASCII(
"video")) {
return Some(SdpMediaSection::MediaType::kVideo);
}
return Nothing();
}
already_AddRefed<RTCRtpTransceiver> PeerConnectionImpl::AddTransceiver(
const dom::RTCRtpTransceiverInit& aInit,
const nsAString& aKind,
dom::MediaStreamTrack* aSendTrack,
bool aAddTrackMagic, ErrorResult& aRv) {
// Copy, because we might need to modify
RTCRtpTransceiverInit init(aInit);
Maybe<SdpMediaSection::MediaType> type = ToSdpMediaType(aKind);
if (NS_WARN_IF(!type.isSome())) {
MOZ_ASSERT(
false,
"Invalid media kind");
aRv = NS_ERROR_INVALID_ARG;
return nullptr;
}
JsepTransceiver jsepTransceiver(*type, *mUuidGen);
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));
aRv = rv;
return nullptr;
}
auto& sendEncodings = init.mSendEncodings;
// CheckAndRectifyEncodings covers these six:
// If any encoding contains a rid member whose value does not conform to the
// grammar requirements specified in Section 10 of [RFC8851], throw a
// TypeError.
// If some but not all encodings contain a rid member, throw a TypeError.
// If any encoding contains a rid member whose value is the same as that of a
// rid contained in another encoding in sendEncodings, throw a TypeError.
// If kind is "audio", remove the scaleResolutionDownBy member from all
// encodings that contain one.
// If any encoding contains a scaleResolutionDownBy member whose value is
// less than 1.0, throw a RangeError.
// Verify that the value of each maxFramerate member in sendEncodings that is
// defined is greater than 0.0. If one of the maxFramerate values does not
// meet this requirement, throw a RangeError.
RTCRtpSender::CheckAndRectifyEncodings(sendEncodings,
*type == SdpMediaSection::kVideo, aRv);
if (aRv.Failed()) {
return nullptr;
}
// If any encoding contains a read-only parameter other than rid, throw an
// InvalidAccessError.
// NOTE: We don't support any additional read-only params right now. Also,
// spec shoehorns this in between checks that setParameters also performs
// (between the rid checks and the scaleResolutionDownBy checks).
// If any encoding contains a scaleResolutionDownBy member, then for each
// encoding without one, add a scaleResolutionDownBy member with the value
// 1.0.
for (
const auto& constEncoding : sendEncodings) {
if (constEncoding.mScaleResolutionDownBy.WasPassed()) {
for (
auto& encoding : sendEncodings) {
if (!encoding.mScaleResolutionDownBy.WasPassed()) {
encoding.mScaleResolutionDownBy.Construct(1.0f);
}
}
break;
}
}
// Let maxN be the maximum number of total simultaneous encodings the user
// agent may support for this kind, at minimum 1.This should be an optimistic
// number since the codec to be used is not known yet.
size_t maxN =
(*type == SdpMediaSection::kVideo) ? webrtc::kMaxSimulcastStreams : 1;
// If the number of encodings stored in sendEncodings exceeds maxN, then trim
// sendEncodings from the tail until its length is maxN.
// NOTE: Spec has this after all validation steps; even if there are elements
// that we will trim off, we still validate them.
if (sendEncodings.Length() > maxN) {
sendEncodings.TruncateLength(maxN);
}
// If kind is "video" and none of the encodings contain a
// scaleResolutionDownBy member, then for each encoding, add a
// scaleResolutionDownBy member with the value 2^(length of sendEncodings -
// encoding index - 1). This results in smaller-to-larger resolutions where
// the last encoding has no scaling applied to it, e.g. 4:2:1 if the length
// is 3.
// NOTE: The code above ensures that these are all set, or all unset, so we
// can just check the first one.
if (sendEncodings.Length() && *type == SdpMediaSection::kVideo &&
!sendEncodings[0].mScaleResolutionDownBy.WasPassed()) {
double scale = 1.0f;
for (
auto it = sendEncodings.rbegin(); it != sendEncodings.rend(); ++it) {
it->mScaleResolutionDownBy.Construct(scale);
scale *= 2;
}
}
// If the number of encodings now stored in sendEncodings is 1, then remove
// any rid member from the lone entry.
if (sendEncodings.Length() == 1) {
sendEncodings[0].mRid.Reset();
}
RefPtr<RTCRtpTransceiver> transceiver = CreateTransceiver(
jsepTransceiver.GetUuid(),
jsepTransceiver.GetMediaType() == SdpMediaSection::kVideo, init,
aSendTrack, aAddTrackMagic, aRv);
if (aRv.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;
}
mTransceivers.AppendElement(transceiver);
return transceiver.forget();
}
bool PeerConnectionImpl::CheckNegotiationNeeded() {
MOZ_ASSERT(mSignalingState == RTCSignalingState::Stable);
SyncToJsep();
return !mLocalIceCredentialsToReplace.empty() ||
mJsepSession->CheckNegotiationNeeded();
}
bool PeerConnectionImpl::CreatedSender(
const dom::RTCRtpSender& aSender)
const {
return aSender.IsMyPc(
this);
}
nsresult PeerConnectionImpl::MaybeInitializeDataChannel() {
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;
DataChannelReliabilityPolicy prPolicy;
switch (aType) {
case IPeerConnection::kDataChannelReliable:
prPolicy = DataChannelReliabilityPolicy::Reliable;
break;
case IPeerConnection::kDataChannelPartialReliableRexmit:
prPolicy = DataChannelReliabilityPolicy::LimitedRetransmissions;
break;
case IPeerConnection::kDataChannelPartialReliableTimed:
prPolicy = DataChannelReliabilityPolicy::LimitedLifetime;
break;
default:
MOZ_ASSERT(
false);
return NS_ERROR_FAILURE;
}
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), prPolicy,
ordered,
prPolicy == DataChannelReliabilityPolicy::LimitedRetransmissions
? aMaxNum
: (prPolicy == DataChannelReliabilityPolicy::LimitedLifetime
? aMaxTime
: 0),
nullptr, nullptr, aExternalNegotiated, aStream);
NS_ENSURE_TRUE(dataChannel, NS_ERROR_NOT_AVAILABLE);
CSFLogDebug(LOGTAG,
"%s: making DOMDataChannel", __FUNCTION__);
Maybe<JsepTransceiver> dcTransceiver =
mJsepSession->FindTransceiver([](
const JsepTransceiver& aTransceiver) {
return aTransceiver.GetMediaType() == SdpMediaSection::kApplication;
});
if (dcTransceiver) {
dcTransceiver->RestartDatachannelTransceiver();
mJsepSession->SetTransceiver(*dcTransceiver);
}
else {
mJsepSession->AddTransceiver(
JsepTransceiver(SdpMediaSection::MediaType::kApplication, *mUuidGen));
}
RefPtr<nsDOMDataChannel> retval;
rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow,
getter_AddRefs(retval));
if (NS_FAILED(rv)) {
return rv;
}
retval.forget(aRetval);
return NS_OK;
}
NS_IMPL_CYCLE_COLLECTION(PeerConnectionImpl::Operation, mPromise, mPc)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl::Operation)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl::Operation)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PeerConnectionImpl::Operation)
PeerConnectionImpl::Operation::Operation(PeerConnectionImpl* aPc,
ErrorResult& aError)
: mPromise(aPc->MakePromise(aError)), mPc(aPc) {}
PeerConnectionImpl::Operation::~Operation() =
default;
void PeerConnectionImpl::Operation::Call(ErrorResult& aError) {
RefPtr<dom::Promise> opPromise = CallImpl(aError);
if (aError.Failed()) {
return;
}
// Upon fulfillment or rejection of the promise returned by the operation,
// run the following steps:
// (NOTE: mPromise is p from https://w3c.github.io/webrtc-pc/#dfn-chain,
// and CallImpl() is what returns the promise for the operation itself)
opPromise->AppendNativeHandler(
this);
}
void PeerConnectionImpl::Operation::ResolvedCallback(
JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
// If connection.[[IsClosed]] is true, abort these steps.
// (the spec wants p to never settle in this event)
if (!mPc->IsClosed()) {
// If the promise returned by operation was fulfilled with a
// value, fulfill p with that value.
mPromise->MaybeResolveWithClone(aCx, aValue);
// Upon fulfillment or rejection of p, execute the following
// steps:
// (Static analysis forces us to use a temporary)
RefPtr<PeerConnectionImpl> pc = mPc;
pc->RunNextOperation(aRv);
}
}
void PeerConnectionImpl::Operation::RejectedCallback(
JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
// If connection.[[IsClosed]] is true, abort these steps.
// (the spec wants p to never settle in this event)
if (!mPc->IsClosed()) {
// If the promise returned by operation was rejected with a
// value, reject p with that value.
mPromise->MaybeRejectWithClone(aCx, aValue);
// Upon fulfillment or rejection of p, execute the following
// steps:
// (Static analysis forces us to use a temporary)
RefPtr<PeerConnectionImpl> pc = mPc;
pc->RunNextOperation(aRv);
}
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(PeerConnectionImpl::JSOperation,
PeerConnectionImpl::Operation, mOperation)
NS_IMPL_ADDREF_INHERITED(PeerConnectionImpl::JSOperation,
PeerConnectionImpl::Operation)
NS_IMPL_RELEASE_INHERITED(PeerConnectionImpl::JSOperation,
PeerConnectionImpl::Operation)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl::JSOperation)
NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation)
PeerConnectionImpl::JSOperation::JSOperation(PeerConnectionImpl* aPc,
dom::ChainedOperation& aOp,
ErrorResult& aError)
: Operation(aPc, aError), mOperation(&aOp) {}
RefPtr<dom::Promise> PeerConnectionImpl::JSOperation::CallImpl(
ErrorResult& aError) {
// Static analysis will not let us call this without a temporary :(
RefPtr<dom::ChainedOperation> op = mOperation;
return op->Call(aError);
}
already_AddRefed<dom::Promise> PeerConnectionImpl::Chain(
dom::ChainedOperation& aOperation, ErrorResult& aError) {
MOZ_RELEASE_ASSERT(!mChainingOperation);
mChainingOperation =
true;
RefPtr<Operation> operation =
new JSOperation(
this, aOperation, aError);
if (aError.Failed()) {
return nullptr;
}
RefPtr<Promise> promise = Chain(operation, aError);
if (aError.Failed()) {
return nullptr;
}
mChainingOperation =
false;
return promise.forget();
}
// This is kinda complicated, but it is what the spec requires us to do. The
// core of what makes this complicated is the requirement that |aOperation| be
// run _immediately_ (without any Promise.Then!) if the operations chain is
// empty.
already_AddRefed<dom::Promise> PeerConnectionImpl::Chain(
const RefPtr<Operation>& aOperation, ErrorResult& aError) {
// If connection.[[IsClosed]] is true, return a promise rejected with a newly
// created InvalidStateError.
if (IsClosed()) {
CSFLogDebug(LOGTAG,
"%s:%d: Peer connection is closed", __FILE__, __LINE__);
RefPtr<dom::Promise> error = MakePromise(aError);
if (aError.Failed()) {
return nullptr;
}
error->MaybeRejectWithInvalidStateError(
"Peer connection is closed");
return error.forget();
}
// Append operation to [[Operations]].
mOperations.AppendElement(aOperation);
// If the length of [[Operations]] is exactly 1, execute operation.
if (mOperations.Length() == 1) {
aOperation->Call(aError);
if (aError.Failed()) {
return nullptr;
}
}
// This is the promise p from https://w3c.github.io/webrtc-pc/#dfn-chain
return do_AddRef(aOperation->GetPromise());
}
void PeerConnectionImpl::RunNextOperation(ErrorResult& aError) {
// If connection.[[IsClosed]] is true, abort these steps.
if (IsClosed()) {
return;
}
// Remove the first element of [[Operations]].
mOperations.RemoveElementAt(0);
// If [[Operations]] is non-empty, execute the operation represented by the
// first element of [[Operations]], and abort these steps.
if (mOperations.Length()) {
// Cannot call without a temporary :(
RefPtr<Operation> op = mOperations[0];
op->Call(aError);
return;
}
// If connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] is false, abort
// these steps.
if (!mUpdateNegotiationNeededFlagOnEmptyChain) {
return;
}
// Set connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to false.
mUpdateNegotiationNeededFlagOnEmptyChain =
false;
// Update the negotiation-needed flag for connection.
UpdateNegotiationNeeded();
}
void PeerConnectionImpl::SyncToJsep() {
for (
const auto& transceiver : mTransceivers) {
transceiver->SyncToJsep(*mJsepSession);
}
}
void PeerConnectionImpl::SyncFromJsep() {
CSFLogDebug(LOGTAG,
"%s", __FUNCTION__);
mJsepSession->ForEachTransceiver(
[
this, self = RefPtr<PeerConnectionImpl>(
this)](
const JsepTransceiver& jsepTransceiver) {
if (jsepTransceiver.GetMediaType() ==
SdpMediaSection::MediaType::kApplication) {
return;
}
CSFLogDebug(LOGTAG,
"%s: Looking for match", __FUNCTION__);
RefPtr<RTCRtpTransceiver> transceiver;
for (
auto& temp : mTransceivers) {
if (temp->GetJsepTransceiverId() == jsepTransceiver.GetUuid()) {
CSFLogDebug(LOGTAG,
"%s: Found match", __FUNCTION__);
transceiver = temp;
break;
}
}
if (!transceiver) {
if (jsepTransceiver.IsRemoved()) {
return;
}
CSFLogDebug(LOGTAG,
"%s: No match, making new", __FUNCTION__);
dom::RTCRtpTransceiverInit init;
init.mDirection = RTCRtpTransceiverDirection::Recvonly;
IgnoredErrorResult rv;
transceiver = CreateTransceiver(
jsepTransceiver.GetUuid(),
jsepTransceiver.GetMediaType() == SdpMediaSection::kVideo, init,
nullptr,
false, rv);
if (NS_WARN_IF(rv.Failed())) {
MOZ_ASSERT(
false);
return;
}
mTransceivers.AppendElement(transceiver);
}
CSFLogDebug(LOGTAG,
"%s: Syncing transceiver", __FUNCTION__);
transceiver->SyncFromJsep(*mJsepSession);
});
}
already_AddRefed<dom::Promise> PeerConnectionImpl::MakePromise(
ErrorResult& aError)
const {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
return dom::Promise::Create(global, aError);
}
void PeerConnectionImpl::UpdateNegotiationNeeded() {
// If the length of connection.[[Operations]] is not 0, then set
// connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and abort
// these steps.
if (mOperations.Length() != 0) {
mUpdateNegotiationNeededFlagOnEmptyChain =
true;
return;
}
// Queue a task to run the following steps:
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
__func__, [
this, self = RefPtr<PeerConnectionImpl>(
this)] {
// If connection.[[IsClosed]] is true, abort these steps.
if (IsClosed()) {
return;
}
// If the length of connection.[[Operations]] is not 0, then set
// connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and
// abort these steps.
if (mOperations.Length()) {
mUpdateNegotiationNeededFlagOnEmptyChain =
true;
return;
}
// If connection's signaling state is not "stable", abort these steps.
if (mSignalingState != RTCSignalingState::Stable) {
return;
}
// If the result of checking if negotiation is needed is false, clear
// the negotiation-needed flag by setting
// connection.[[NegotiationNeeded]] to false, and abort these steps.
if (!CheckNegotiationNeeded()) {
mNegotiationNeeded =
false;
return;
}
// If connection.[[NegotiationNeeded]] is already true, abort these
// steps.
if (mNegotiationNeeded) {
return;
}
// Set connection.[[NegotiationNeeded]] to true.
mNegotiationNeeded =
true;
// Fire an event named negotiationneeded at connection.
ErrorResult rv;
mPCObserver->FireNegotiationNeededEvent(rv);
}));
}
RefPtr<dom::RTCRtpTransceiver> PeerConnectionImpl::GetTransceiver(
const std::string& aTransceiverId) {
for (
const auto& transceiver : mTransceivers) {
if (transceiver->GetJsepTransceiverId() == aTransceiverId) {
return transceiver;
}
}
return nullptr;
}
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);
}
void PeerConnectionImpl::NotifyDataChannelOpen(DataChannel*) {
mDataChannelsOpened++;
}
void PeerConnectionImpl::NotifyDataChannelClosed(DataChannel*) {
mDataChannelsClosed++;
}
void PeerConnectionImpl::NotifySctpConnected() {
if (!mSctpTransport) {
MOZ_ASSERT(
false);
return;
}
mSctpTransport->UpdateState(RTCSctpTransportState::Connected);
}
void PeerConnectionImpl::NotifySctpClosed() {
if (!mSctpTransport) {
MOZ_ASSERT(
false);
return;
}
mSctpTransport->UpdateState(RTCSctpTransportState::Closed);
}
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 ||
!mLocalIceCredentialsToReplace.empty());
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");
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
__func__, [
this, self = RefPtr<PeerConnectionImpl>(
this), aOptions] {
std::string offer;
SyncToJsep();
UniquePtr<JsepSession> uncommittedJsepSession(mJsepSession->Clone());
JsepSession::Result result =
uncommittedJsepSession->CreateOffer(aOptions, &offer);
JSErrorResult rv;
if (result.mError.isSome()) {
std::string errorString = uncommittedJsepSession->GetLastError();
CSFLogError(LOGTAG,
"%s: pc = %s, error = %s", __FUNCTION__,
mHandle.c_str(), errorString.c_str());
mPCObserver->OnCreateOfferError(
*buildJSErrorData(result, errorString), rv);
}
else {
mJsepSession = std::move(uncommittedJsepSession);
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;
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
__func__, [
this, self = RefPtr<PeerConnectionImpl>(
this), options] {
std::string answer;
SyncToJsep();
UniquePtr<JsepSession> uncommittedJsepSession(mJsepSession->Clone());
JsepSession::Result result =
uncommittedJsepSession->CreateAnswer(options, &answer);
JSErrorResult rv;
if (result.mError.isSome()) {
std::string errorString = uncommittedJsepSession->GetLastError();
CSFLogError(LOGTAG,
"%s: pc = %s, error = %s", __FUNCTION__,
mHandle.c_str(), errorString.c_str());
mPCObserver->OnCreateAnswerError(
*buildJSErrorData(result, errorString), rv);
}
else {
mJsepSession = std::move(uncommittedJsepSession);
mPCObserver->OnCreateAnswerSuccess(ObString(answer.c_str()), rv);
}
}));
return NS_OK;
}
dom::RTCSdpType ToDomSdpType(JsepSdpType aType) {
switch (aType) {
case kJsepSdpOffer:
return dom::RTCSdpType::Offer;
case kJsepSdpAnswer:
return dom::RTCSdpType::Answer;
case kJsepSdpPranswer:
return dom::RTCSdpType::Pranswer;
case kJsepSdpRollback:
return dom::RTCSdpType::Rollback;
}
MOZ_CRASH(
"Nonexistent JsepSdpType");
}
JsepSdpType ToJsepSdpType(dom::RTCSdpType aType) {
switch (aType) {
case dom::RTCSdpType::Offer:
return kJsepSdpOffer;
case dom::RTCSdpType::Pranswer:
return kJsepSdpPranswer;
case dom::RTCSdpType::Answer:
return kJsepSdpAnswer;
case dom::RTCSdpType::Rollback:
return kJsepSdpRollback;
}
MOZ_CRASH(
"Nonexistent dom::RTCSdpType");
}
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;
}
STAMP_TIMECARD(mTimeCard,
"Set Local Description");
if (AnyLocalTrackHasPeerIdentity()) {
mRequestedPrivacy = Some(PrincipalPrivacy::
Private);
}
mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry;
sdpEntry.mIsLocal =
true;
sdpEntry.mTimestamp = mTimestampMaker.GetNow().ToDom();
sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP);
auto appendHistory = [&]() {
if (!mSdpHistory.AppendElement(sdpEntry, fallible)) {
mozalloc_handle_oom(0);
}
};
mLocalRequestedSDP = aSDP;
SyncToJsep();
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;
}
MOZ_ASSERT(!mUncommittedJsepSession);
mUncommittedJsepSession.reset(mJsepSession->Clone());
JsepSession::Result result =
mUncommittedJsepSession->SetLocalDescription(sdpType, mLocalRequestedSDP);
JSErrorResult rv;
if (result.mError.isSome()) {
std::string errorString = mUncommittedJsepSession->GetLastError();
mUncommittedJsepSession = nullptr;
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);
}
mPCObserver->OnSetDescriptionSuccess(rv);
}
appendHistory();
if (rv.Failed()) {
return rv.StealNSResult();
}
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;
}
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().ToDom();
sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP);
auto appendHistory = [&]() {
if (!mSdpHistory.AppendElement(sdpEntry, fallible)) {
mozalloc_handle_oom(0);
}
};
SyncToJsep();
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;
}
MOZ_ASSERT(!mUncommittedJsepSession);
mUncommittedJsepSession.reset(mJsepSession->Clone());
JsepSession::Result result = mUncommittedJsepSession->SetRemoteDescription(
sdpType, mRemoteRequestedSDP);
JSErrorResult jrv;
if (result.mError.isSome()) {
std::string errorString = mUncommittedJsepSession->GetLastError();
mUncommittedJsepSession = nullptr;
sdpEntry.mErrors = GetLastSdpParsingErrors();
CSFLogError(LOGTAG,
"%s: pc = %s, error = %s", __FUNCTION__,
mHandle.c_str(), errorString.c_str());
mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString),
jrv);
}
else {
if (wasRestartingIce) {
RecordIceRestartStatistics(sdpType);
}
mPCObserver->OnSetDescriptionSuccess(jrv);
}
appendHistory();
if (jrv.Failed()) {
return jrv.StealNSResult();
}
return NS_OK;
}
already_AddRefed<dom::Promise> PeerConnectionImpl::GetStats(
MediaStreamTrack* aSelector) {
if (!mWindow) {
MOZ_CRASH(
"Cannot create a promise without a window!");
}
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
ErrorResult rv;
RefPtr<Promise> promise = Promise::Create(global, rv);
if (NS_WARN_IF(rv.Failed())) {
MOZ_CRASH(
"Failed to create a promise!");
}
if (!IsClosed()) {
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));
});
}
else {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
}
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());
}
MOZ_DIAGNOSTIC_ASSERT(
!mUncommittedJsepSession,
"AddIceCandidate is chained, which means it should never "
"run while an sRD/sLD is in progress");
JsepSession::Result result = mJsepSession->AddRemoteIceCandidate(
aCandidate, aMid, level, aUfrag, &transportId);
if (!result.mError.isSome()) {
// We do not bother the MediaTransportHandler about this before
// offer/answer concludes. Once offer/answer concludes, we will extract
// these candidates from the remote SDP.
if (mSignalingState == RTCSignalingState::Stable && !transportId.empty()) {
AddIceCandidate(aCandidate, transportId, aUfrag);
mRawTrickledCandidates.push_back(aCandidate);
}
// Spec says we queue a task for these updates
GetMainThreadSerialEventTarget()->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 {
--> --------------------
--> maximum size reached
--> --------------------