summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp')
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp714
1 files changed, 714 insertions, 0 deletions
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp
new file mode 100644
index 0000000000..25bcc2a6b0
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp
@@ -0,0 +1,714 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcGlobalInformation.h"
+#include "mozilla/media/webrtc/WebrtcGlobal.h"
+#include "WebrtcGlobalChild.h"
+#include "WebrtcGlobalParent.h"
+
+#include <algorithm>
+#include <vector>
+#include <type_traits>
+
+#include "mozilla/dom/WebrtcGlobalInformationBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h" // for RTCStatsReportInternal
+#include "mozilla/dom/ContentChild.h"
+
+#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID
+#include "nsServiceManagerUtils.h" // do_GetService
+#include "mozilla/ErrorResult.h"
+#include "nsProxyRelease.h" // nsMainThreadPtrHolder
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+
+#include "common/browser_logging/WebRtcLog.h"
+#include "transport/runnable_utils.h"
+#include "MediaTransportHandler.h"
+#include "PeerConnectionCtx.h"
+#include "PeerConnectionImpl.h"
+
+namespace mozilla::dom {
+
+typedef nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback>
+ StatsRequestCallback;
+
+typedef nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback> LogRequestCallback;
+
+class WebrtcContentParents {
+ public:
+ static WebrtcGlobalParent* Alloc();
+ static void Dealloc(WebrtcGlobalParent* aParent);
+ static bool Empty() { return sContentParents.empty(); }
+ static const std::vector<RefPtr<WebrtcGlobalParent>>& GetAll() {
+ return sContentParents;
+ }
+
+ private:
+ static std::vector<RefPtr<WebrtcGlobalParent>> sContentParents;
+ WebrtcContentParents() = delete;
+ WebrtcContentParents(const WebrtcContentParents&) = delete;
+ WebrtcContentParents& operator=(const WebrtcContentParents&) = delete;
+};
+
+std::vector<RefPtr<WebrtcGlobalParent>> WebrtcContentParents::sContentParents;
+
+WebrtcGlobalParent* WebrtcContentParents::Alloc() {
+ RefPtr<WebrtcGlobalParent> cp = new WebrtcGlobalParent;
+ sContentParents.push_back(cp);
+ return cp.get();
+}
+
+void WebrtcContentParents::Dealloc(WebrtcGlobalParent* aParent) {
+ if (aParent) {
+ aParent->mShutdown = true;
+ auto cp =
+ std::find(sContentParents.begin(), sContentParents.end(), aParent);
+ if (cp != sContentParents.end()) {
+ sContentParents.erase(cp);
+ }
+ }
+}
+
+static PeerConnectionCtx* GetPeerConnectionCtx() {
+ if (PeerConnectionCtx::isActive()) {
+ MOZ_ASSERT(PeerConnectionCtx::GetInstance());
+ return PeerConnectionCtx::GetInstance();
+ }
+ return nullptr;
+}
+
+static RefPtr<PWebrtcGlobalParent::GetStatsPromise>
+GetStatsPromiseForThisProcess(const nsAString& aPcIdFilter) {
+ nsTArray<RefPtr<dom::RTCStatsReportPromise>> promises;
+
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ if (ctx) {
+ // Grab stats for non-closed PCs
+ for (const auto& [id, pc] : ctx->GetPeerConnections()) {
+ if (aPcIdFilter.IsEmpty() || aPcIdFilter.EqualsASCII(id.c_str())) {
+ if (pc->HasMedia()) {
+ promises.AppendElement(pc->GetStats(nullptr, true));
+ }
+ }
+ }
+
+ // Grab stats for closed PCs
+ for (const auto& report : ctx->mStatsForClosedPeerConnections) {
+ if (aPcIdFilter.IsEmpty() || aPcIdFilter == report.mPcid) {
+ promises.AppendElement(dom::RTCStatsReportPromise::CreateAndResolve(
+ MakeUnique<dom::RTCStatsReportInternal>(report), __func__));
+ }
+ }
+ }
+
+ auto UnwrapUniquePtrs = [](dom::RTCStatsReportPromise::AllSettledPromiseType::
+ ResolveOrRejectValue&& aResult) {
+ nsTArray<dom::RTCStatsReportInternal> reports;
+ MOZ_RELEASE_ASSERT(aResult.IsResolve(), "AllSettled should never reject!");
+ for (auto& reportResult : aResult.ResolveValue()) {
+ if (reportResult.IsResolve()) {
+ reports.AppendElement(*reportResult.ResolveValue());
+ }
+ }
+ return PWebrtcGlobalParent::GetStatsPromise::CreateAndResolve(
+ std::move(reports), __func__);
+ };
+
+ return dom::RTCStatsReportPromise::AllSettled(
+ GetMainThreadSerialEventTarget(), promises)
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ std::move(UnwrapUniquePtrs));
+}
+
+static nsTArray<dom::RTCStatsReportInternal>& GetWebrtcGlobalStatsStash() {
+ static StaticAutoPtr<nsTArray<dom::RTCStatsReportInternal>> sStash;
+ if (!sStash) {
+ sStash = new nsTArray<dom::RTCStatsReportInternal>();
+ ClearOnShutdown(&sStash);
+ }
+ return *sStash;
+}
+
+static std::map<int32_t, dom::Sequence<nsString>>& GetWebrtcGlobalLogStash() {
+ static StaticAutoPtr<std::map<int32_t, dom::Sequence<nsString>>> sStash;
+ if (!sStash) {
+ sStash = new std::map<int32_t, dom::Sequence<nsString>>();
+ ClearOnShutdown(&sStash);
+ }
+ return *sStash;
+}
+
+static void ClearClosedStats() {
+ GetWebrtcGlobalStatsStash().Clear();
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+
+ if (ctx) {
+ ctx->mStatsForClosedPeerConnections.Clear();
+ }
+}
+
+void WebrtcGlobalInformation::ClearAllStats(const GlobalObject& aGlobal) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ // Chrome-only API
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!WebrtcContentParents::Empty()) {
+ // Pass on the request to any content process based PeerConnections.
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendClearStats();
+ }
+ }
+
+ // Flush the history for the chrome process
+ ClearClosedStats();
+}
+
+void WebrtcGlobalInformation::GetAllStats(
+ const GlobalObject& aGlobal, WebrtcGlobalStatisticsCallback& aStatsCallback,
+ const Optional<nsAString>& pcIdFilter, ErrorResult& aRv) {
+ if (!NS_IsMainThread()) {
+ aRv.Throw(NS_ERROR_NOT_SAME_THREAD);
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsTArray<RefPtr<PWebrtcGlobalParent::GetStatsPromise>> statsPromises;
+
+ nsString filter;
+ if (pcIdFilter.WasPassed()) {
+ filter = pcIdFilter.Value();
+ }
+
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ statsPromises.AppendElement(cp->SendGetStats(filter));
+ }
+
+ // Stats from this (the parent) process. How long do we keep supporting this?
+ statsPromises.AppendElement(GetStatsPromiseForThisProcess(filter));
+
+ // CallbackObject does not support threadsafe refcounting, and must be
+ // used and destroyed on main.
+ StatsRequestCallback callbackHandle(
+ new nsMainThreadPtrHolder<WebrtcGlobalStatisticsCallback>(
+ "WebrtcGlobalStatisticsCallback", &aStatsCallback));
+
+ auto FlattenThenStashThenCallback =
+ [callbackHandle,
+ filter](PWebrtcGlobalParent::GetStatsPromise::AllSettledPromiseType::
+ ResolveOrRejectValue&& aResult) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ std::set<nsString> pcids;
+ WebrtcGlobalStatisticsReport flattened;
+ MOZ_RELEASE_ASSERT(aResult.IsResolve(),
+ "AllSettled should never reject!");
+ for (auto& contentProcessResult : aResult.ResolveValue()) {
+ // TODO: Report rejection on individual content processes someday?
+ if (contentProcessResult.IsResolve()) {
+ for (auto& pcStats : contentProcessResult.ResolveValue()) {
+ pcids.insert(pcStats.mPcid);
+ if (!flattened.mReports.AppendElement(std::move(pcStats),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ }
+
+ if (filter.IsEmpty()) {
+ // Unfiltered is pretty simple; add stuff from stash that is
+ // missing, then stomp the stash with the new reports.
+ for (auto& pcStats : GetWebrtcGlobalStatsStash()) {
+ if (!pcids.count(pcStats.mPcid)) {
+ // Stats from a closed PC or stopped content process.
+ // Content process may have gone away before we got to update
+ // this.
+ pcStats.mClosed = true;
+ if (!flattened.mReports.AppendElement(std::move(pcStats),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ GetWebrtcGlobalStatsStash() = flattened.mReports;
+ } else {
+ // Filtered is slightly more complex
+ if (flattened.mReports.IsEmpty()) {
+ // Find entry from stash and add it to report
+ for (auto& pcStats : GetWebrtcGlobalStatsStash()) {
+ if (pcStats.mPcid == filter) {
+ pcStats.mClosed = true;
+ if (!flattened.mReports.AppendElement(std::move(pcStats),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ } else {
+ // Find entries in stash, remove them, and then add new entries
+ for (size_t i = 0; i < GetWebrtcGlobalStatsStash().Length();) {
+ auto& pcStats = GetWebrtcGlobalStatsStash()[i];
+ if (pcStats.mPcid == filter) {
+ GetWebrtcGlobalStatsStash().RemoveElementAt(i);
+ } else {
+ ++i;
+ }
+ }
+ GetWebrtcGlobalStatsStash().AppendElements(flattened.mReports);
+ }
+ }
+
+ IgnoredErrorResult rv;
+ callbackHandle->Call(flattened, rv);
+ };
+
+ PWebrtcGlobalParent::GetStatsPromise::AllSettled(
+ GetMainThreadSerialEventTarget(), statsPromises)
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ std::move(FlattenThenStashThenCallback));
+
+ aRv = NS_OK;
+}
+
+static RefPtr<PWebrtcGlobalParent::GetLogPromise> GetLogPromise() {
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ if (!ctx) {
+ // This process has never created a PeerConnection, so no ICE logging.
+ return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve(
+ Sequence<nsString>(), __func__);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISerialEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_WARN_IF(NS_FAILED(rv) || !stsThread)) {
+ return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve(
+ Sequence<nsString>(), __func__);
+ }
+
+ RefPtr<MediaTransportHandler> transportHandler = ctx->GetTransportHandler();
+
+ auto AddMarkers =
+ [](MediaTransportHandler::IceLogPromise::ResolveOrRejectValue&& aValue) {
+ nsString pid;
+ pid.AppendInt(getpid());
+ Sequence<nsString> logs;
+ if (aValue.IsResolve() && !aValue.ResolveValue().IsEmpty()) {
+ bool ok = logs.AppendElement(
+ u"+++++++ BEGIN (process id "_ns + pid + u") ++++++++"_ns,
+ fallible);
+ ok &=
+ !!logs.AppendElements(std::move(aValue.ResolveValue()), fallible);
+ ok &= !!logs.AppendElement(
+ u"+++++++ END (process id "_ns + pid + u") ++++++++"_ns,
+ fallible);
+ if (!ok) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve(
+ std::move(logs), __func__);
+ };
+
+ return transportHandler->GetIceLog(nsCString())
+ ->Then(GetMainThreadSerialEventTarget(), __func__, std::move(AddMarkers));
+}
+
+static nsresult RunLogClear() {
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ if (!ctx) {
+ // This process has never created a PeerConnection, so no ICE logging.
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISerialEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!stsThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<MediaTransportHandler> transportHandler = ctx->GetTransportHandler();
+
+ return RUN_ON_THREAD(
+ stsThread,
+ WrapRunnable(transportHandler, &MediaTransportHandler::ClearIceLog),
+ NS_DISPATCH_NORMAL);
+}
+
+void WebrtcGlobalInformation::ClearLogging(const GlobalObject& aGlobal) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ // Chrome-only API
+ MOZ_ASSERT(XRE_IsParentProcess());
+ GetWebrtcGlobalLogStash().clear();
+
+ if (!WebrtcContentParents::Empty()) {
+ // Clear content process signaling logs
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendClearLog();
+ }
+ }
+
+ // Clear chrome process signaling logs
+ Unused << RunLogClear();
+}
+
+static RefPtr<GenericPromise> UpdateLogStash() {
+ nsTArray<RefPtr<GenericPromise>> logPromises;
+ MOZ_ASSERT(XRE_IsParentProcess());
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ auto StashLog =
+ [id = cp->Id() * 2 /* Make sure 1 isn't used */](
+ PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve() && !aValue.ResolveValue().IsEmpty()) {
+ GetWebrtcGlobalLogStash()[id] = aValue.ResolveValue();
+ }
+ return GenericPromise::CreateAndResolve(true, __func__);
+ };
+ logPromises.AppendElement(cp->SendGetLog()->Then(
+ GetMainThreadSerialEventTarget(), __func__, std::move(StashLog)));
+ }
+
+ // Get ICE logging for this (the parent) process. How long do we support this?
+ logPromises.AppendElement(GetLogPromise()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [](PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve()) {
+ GetWebrtcGlobalLogStash()[1] = aValue.ResolveValue();
+ }
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }));
+
+ return GenericPromise::AllSettled(GetMainThreadSerialEventTarget(),
+ logPromises)
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ [](GenericPromise::AllSettledPromiseType::ResolveOrRejectValue&&
+ aValue) {
+ // We don't care about the value, since we're just going to copy
+ // what is in the stash. This ignores failures too, which is what
+ // we want.
+ return GenericPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+void WebrtcGlobalInformation::GetLogging(
+ const GlobalObject& aGlobal, const nsAString& aPattern,
+ WebrtcGlobalLoggingCallback& aLoggingCallback, ErrorResult& aRv) {
+ if (!NS_IsMainThread()) {
+ aRv.Throw(NS_ERROR_NOT_SAME_THREAD);
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsAutoCString pattern;
+ CopyUTF16toUTF8(aPattern, pattern);
+
+ // CallbackObject does not support threadsafe refcounting, and must be
+ // destroyed on main.
+ LogRequestCallback callbackHandle(
+ new nsMainThreadPtrHolder<WebrtcGlobalLoggingCallback>(
+ "WebrtcGlobalLoggingCallback", &aLoggingCallback));
+
+ auto FilterThenCallback =
+ [pattern, callbackHandle](GenericPromise::ResolveOrRejectValue&& aValue)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ dom::Sequence<nsString> flattened;
+ for (const auto& [id, log] : GetWebrtcGlobalLogStash()) {
+ (void)id;
+ for (const auto& line : log) {
+ if (pattern.IsEmpty() || (line.Find(pattern) != kNotFound)) {
+ if (!flattened.AppendElement(line, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ }
+ IgnoredErrorResult rv;
+ callbackHandle->Call(flattened, rv);
+ };
+
+ UpdateLogStash()->Then(GetMainThreadSerialEventTarget(), __func__,
+ std::move(FilterThenCallback));
+ aRv = NS_OK;
+}
+
+static int32_t sLastSetLevel = 0;
+static bool sLastAECDebug = false;
+static Maybe<nsCString> sAecDebugLogDir;
+
+void WebrtcGlobalInformation::SetDebugLevel(const GlobalObject& aGlobal,
+ int32_t aLevel) {
+ if (aLevel) {
+ StartWebRtcLog(mozilla::LogLevel(aLevel));
+ } else {
+ StopWebRtcLog();
+ }
+ sLastSetLevel = aLevel;
+
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendSetDebugMode(aLevel);
+ }
+}
+
+int32_t WebrtcGlobalInformation::DebugLevel(const GlobalObject& aGlobal) {
+ return sLastSetLevel;
+}
+
+void WebrtcGlobalInformation::SetAecDebug(const GlobalObject& aGlobal,
+ bool aEnable) {
+ if (aEnable) {
+ sAecDebugLogDir = Some(StartAecLog());
+ } else {
+ StopAecLog();
+ }
+
+ sLastAECDebug = aEnable;
+
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendSetAecLogging(aEnable);
+ }
+}
+
+bool WebrtcGlobalInformation::AecDebug(const GlobalObject& aGlobal) {
+ return sLastAECDebug;
+}
+
+void WebrtcGlobalInformation::GetAecDebugLogDir(const GlobalObject& aGlobal,
+ nsAString& aDir) {
+ aDir = NS_ConvertASCIItoUTF16(sAecDebugLogDir.valueOr(""_ns));
+}
+
+WebrtcGlobalParent* WebrtcGlobalParent::Alloc() {
+ return WebrtcContentParents::Alloc();
+}
+
+bool WebrtcGlobalParent::Dealloc(WebrtcGlobalParent* aActor) {
+ WebrtcContentParents::Dealloc(aActor);
+ return true;
+}
+
+void WebrtcGlobalParent::ActorDestroy(ActorDestroyReason aWhy) {
+ mShutdown = true;
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalParent::Recv__delete__() {
+ return IPC_OK();
+}
+
+MOZ_IMPLICIT WebrtcGlobalParent::WebrtcGlobalParent() : mShutdown(false) {
+ MOZ_COUNT_CTOR(WebrtcGlobalParent);
+}
+
+MOZ_IMPLICIT WebrtcGlobalParent::~WebrtcGlobalParent() {
+ MOZ_COUNT_DTOR(WebrtcGlobalParent);
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvGetStats(
+ const nsString& aPcIdFilter, GetStatsResolver&& aResolve) {
+ if (!mShutdown) {
+ GetStatsPromiseForThisProcess(aPcIdFilter)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [resolve = std::move(aResolve)](
+ nsTArray<dom::RTCStatsReportInternal>&& aReports) {
+ resolve(std::move(aReports));
+ },
+ []() { MOZ_CRASH(); });
+ return IPC_OK();
+ }
+
+ aResolve(nsTArray<RTCStatsReportInternal>());
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvClearStats() {
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ ClearClosedStats();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvGetLog(
+ GetLogResolver&& aResolve) {
+ if (mShutdown) {
+ aResolve(Sequence<nsString>());
+ return IPC_OK();
+ }
+
+ GetLogPromise()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aResolve = std::move(aResolve)](
+ PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve()) {
+ aResolve(aValue.ResolveValue());
+ } else {
+ aResolve(Sequence<nsString>());
+ }
+ });
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvClearLog() {
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ RunLogClear();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvSetAecLogging(
+ const bool& aEnable) {
+ if (!mShutdown) {
+ if (aEnable) {
+ StartAecLog();
+ } else {
+ StopAecLog();
+ }
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvSetDebugMode(const int& aLevel) {
+ if (!mShutdown) {
+ if (aLevel) {
+ StartWebRtcLog(mozilla::LogLevel(aLevel));
+ } else {
+ StopWebRtcLog();
+ }
+ }
+ return IPC_OK();
+}
+
+WebrtcGlobalChild* WebrtcGlobalChild::Create() {
+ WebrtcGlobalChild* child = static_cast<WebrtcGlobalChild*>(
+ ContentChild::GetSingleton()->SendPWebrtcGlobalConstructor());
+ return child;
+}
+
+void WebrtcGlobalChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mShutdown = true;
+}
+
+MOZ_IMPLICIT WebrtcGlobalChild::WebrtcGlobalChild() : mShutdown(false) {
+ MOZ_COUNT_CTOR(WebrtcGlobalChild);
+}
+
+MOZ_IMPLICIT WebrtcGlobalChild::~WebrtcGlobalChild() {
+ MOZ_COUNT_DTOR(WebrtcGlobalChild);
+}
+
+static void StoreLongTermICEStatisticsImpl_m(RTCStatsReportInternal* report) {
+ using namespace Telemetry;
+
+ report->mClosed = true;
+
+ for (const auto& outboundRtpStats : report->mOutboundRtpStreamStats) {
+ bool isVideo = (outboundRtpStats.mId.Value().Find("video") != -1);
+ if (!isVideo) {
+ continue;
+ }
+ if (outboundRtpStats.mBitrateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_AVG_PER_CALL_KBPS,
+ uint32_t(outboundRtpStats.mBitrateMean.Value() / 1000));
+ }
+ if (outboundRtpStats.mBitrateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_STD_DEV_PER_CALL_KBPS,
+ uint32_t(outboundRtpStats.mBitrateStdDev.Value() / 1000));
+ }
+ if (outboundRtpStats.mFramerateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_AVG_PER_CALL,
+ uint32_t(outboundRtpStats.mFramerateMean.Value()));
+ }
+ if (outboundRtpStats.mFramerateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_10X_STD_DEV_PER_CALL,
+ uint32_t(outboundRtpStats.mFramerateStdDev.Value() * 10));
+ }
+ if (outboundRtpStats.mDroppedFrames.WasPassed() &&
+ report->mCallDurationMs.WasPassed()) {
+ double mins = report->mCallDurationMs.Value() / (1000 * 60);
+ if (mins > 0) {
+ Accumulate(
+ WEBRTC_VIDEO_ENCODER_DROPPED_FRAMES_PER_CALL_FPM,
+ uint32_t(double(outboundRtpStats.mDroppedFrames.Value()) / mins));
+ }
+ }
+ }
+
+ for (const auto& inboundRtpStats : report->mInboundRtpStreamStats) {
+ bool isVideo = (inboundRtpStats.mId.Value().Find("video") != -1);
+ if (!isVideo) {
+ continue;
+ }
+ if (inboundRtpStats.mBitrateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_AVG_PER_CALL_KBPS,
+ uint32_t(inboundRtpStats.mBitrateMean.Value() / 1000));
+ }
+ if (inboundRtpStats.mBitrateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_STD_DEV_PER_CALL_KBPS,
+ uint32_t(inboundRtpStats.mBitrateStdDev.Value() / 1000));
+ }
+ if (inboundRtpStats.mFramerateMean.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_AVG_PER_CALL,
+ uint32_t(inboundRtpStats.mFramerateMean.Value()));
+ }
+ if (inboundRtpStats.mFramerateStdDev.WasPassed()) {
+ Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_10X_STD_DEV_PER_CALL,
+ uint32_t(inboundRtpStats.mFramerateStdDev.Value() * 10));
+ }
+ if (inboundRtpStats.mDiscardedPackets.WasPassed() &&
+ report->mCallDurationMs.WasPassed()) {
+ double mins = report->mCallDurationMs.Value() / (1000 * 60);
+ if (mins > 0) {
+ Accumulate(
+ WEBRTC_VIDEO_DECODER_DISCARDED_PACKETS_PER_CALL_PPM,
+ uint32_t(double(inboundRtpStats.mDiscardedPackets.Value()) / mins));
+ }
+ }
+ }
+
+ // Finally, store the stats
+
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ if (ctx) {
+ if (!ctx->mStatsForClosedPeerConnections.AppendElement(*report, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+}
+
+void WebrtcGlobalInformation::StoreLongTermICEStatistics(
+ PeerConnectionImpl& aPc) {
+ if (aPc.IceConnectionState() == RTCIceConnectionState::New) {
+ // ICE has not started; we won't have any remote candidates, so recording
+ // statistics on gathered candidates is pointless.
+ return;
+ }
+
+ aPc.GetStats(nullptr, true)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [=](UniquePtr<dom::RTCStatsReportInternal>&& aReport) {
+ StoreLongTermICEStatisticsImpl_m(aReport.get());
+ },
+ [=](nsresult aError) {});
+}
+
+} // namespace mozilla::dom