Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/media/webrtc/jsapi/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 168 kB image not shown  

Quelle  PeerConnectionImpl.cpp   Sprache: C

 
/* 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

--> --------------------

Messung V0.5
C=91 H=97 G=93

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.