static void OnStatsReport_m(
  nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback> aStatsCallback,
  nsAutoPtr<RTCStatsQueries> aQueryList)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aQueryList);

  WebrtcGlobalStatisticsReport report;
  report.mReports.Construct();

  // Reports for the currently active PeerConnections
  for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) {
    MOZ_ASSERT(*q);
    report.mReports.Value().AppendElement(*(*q)->report);
  }

  PeerConnectionCtx* ctx = GetPeerConnectionCtx();
  if (ctx) {
    // Reports for closed/destroyed PeerConnections
    report.mReports.Value().AppendElements(ctx->mStatsForClosedPeerConnections);
  }

  ErrorResult rv;
  aStatsCallback.get()->Call(report, rv);

  if (rv.Failed()) {
    CSFLogError(logTag, "Error firing stats observer callback");
  }
}
static void GetAllStats_s(
  nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback> aStatsCallback,
  nsAutoPtr<RTCStatsQueries> aQueryList)
{
  MOZ_ASSERT(aQueryList);

  for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) {
    MOZ_ASSERT(*q);
    PeerConnectionImpl::ExecuteStatsQuery_s(*q);
  }

  NS_DispatchToMainThread(WrapRunnableNM(&OnStatsReport_m,
                                         aStatsCallback,
                                         aQueryList),
                          NS_DISPATCH_NORMAL);
}
static void OnGetLogging_m(
  nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback> aLoggingCallback,
  const std::string& aPattern,
  nsAutoPtr<std::deque<std::string>> aLogList)
{
  ErrorResult rv;
  if (!aLogList->empty()) {
    Sequence<nsString> nsLogs;
    for (auto l = aLogList->begin(); l != aLogList->end(); ++l) {
      nsLogs.AppendElement(NS_ConvertUTF8toUTF16(l->c_str()));
    }
    aLoggingCallback.get()->Call(nsLogs, rv);
  }

  if (rv.Failed()) {
    CSFLogError(logTag, "Error firing logging observer callback");
  }
}
static void
EverySecondTelemetryCallback_s(nsAutoPtr<RTCStatsQueries> aQueryList) {
  using namespace Telemetry;

  if(!PeerConnectionCtx::isActive()) {
    return;
  }
  PeerConnectionCtx *ctx = PeerConnectionCtx::GetInstance();

  for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) {
    PeerConnectionImpl::ExecuteStatsQuery_s(*q);
    auto& r = *(*q)->report;
    if (r.mInboundRTPStreamStats.WasPassed()) {
      // First, get reports from a second ago, if any, for calculations below
      const Sequence<RTCInboundRTPStreamStats> *lastInboundStats = nullptr;
      {
        auto i = FindId(ctx->mLastReports, r.mPcid);
        if (i != ctx->mLastReports.NoIndex) {
          lastInboundStats = &ctx->mLastReports[i]->mInboundRTPStreamStats.Value();
        }
      }
      // Then, look for the things we want telemetry on
      auto& array = r.mInboundRTPStreamStats.Value();
      for (decltype(array.Length()) i = 0; i < array.Length(); i++) {
        auto& s = array[i];
        bool isAudio = (s.mId.Value().Find("audio") != -1);
        if (s.mPacketsLost.WasPassed()) {
          Accumulate(s.mIsRemote?
                     (isAudio? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS :
                               WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS) :
                     (isAudio? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS :
                               WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS),
                      s.mPacketsLost.Value());
        }
        if (s.mJitter.WasPassed()) {
          Accumulate(s.mIsRemote?
                     (isAudio? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER :
                               WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER) :
                     (isAudio? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER :
                               WEBRTC_VIDEO_QUALITY_INBOUND_JITTER),
                      s.mJitter.Value());
        }
        if (s.mMozRtt.WasPassed()) {
          MOZ_ASSERT(s.mIsRemote);
          Accumulate(isAudio? WEBRTC_AUDIO_QUALITY_OUTBOUND_RTT :
                              WEBRTC_VIDEO_QUALITY_OUTBOUND_RTT,
                      s.mMozRtt.Value());
        }
        if (lastInboundStats && s.mBytesReceived.WasPassed()) {
          auto& laststats = *lastInboundStats;
          auto i = FindId(laststats, s.mId.Value());
          if (i != laststats.NoIndex) {
            auto& lasts = laststats[i];
            if (lasts.mBytesReceived.WasPassed()) {
              auto delta_ms = int32_t(s.mTimestamp.Value() -
                                      lasts.mTimestamp.Value());
              if (delta_ms > 0 && delta_ms < 60000) {
                Accumulate(s.mIsRemote?
                           (isAudio? WEBRTC_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS :
                                     WEBRTC_VIDEO_QUALITY_OUTBOUND_BANDWIDTH_KBITS) :
                           (isAudio? WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS :
                                     WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS),
                           ((s.mBytesReceived.Value() -
                             lasts.mBytesReceived.Value()) * 8) / delta_ms);
              }
            }
          }
        }
      }
    }
  }
  // Steal and hang on to reports for the next second
  ctx->mLastReports.Clear();
  for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) {
    ctx->mLastReports.AppendElement((*q)->report.forget()); // steal avoids copy
  }
  // Container must be freed back on main thread
  NS_DispatchToMainThread(WrapRunnableNM(&FreeOnMain_m, aQueryList),
                          NS_DISPATCH_NORMAL);
}
static void
EverySecondTelemetryCallback_s(nsAutoPtr<RTCStatsQueries> aQueryList) {
  using namespace Telemetry;

  if(!PeerConnectionCtx::isActive()) {
    return;
  }
  PeerConnectionCtx *ctx = PeerConnectionCtx::GetInstance();

  for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) {
    PeerConnectionImpl::ExecuteStatsQuery_s(*q);
    auto& r = *(*q)->report;
    bool isHello = (*q)->isHello;
    if (r.mInboundRTPStreamStats.WasPassed()) {
      // First, get reports from a second ago, if any, for calculations below
      const Sequence<RTCInboundRTPStreamStats> *lastInboundStats = nullptr;
      {
        auto i = FindId(ctx->mLastReports, r.mPcid);
        if (i != ctx->mLastReports.NoIndex) {
          lastInboundStats = &ctx->mLastReports[i]->mInboundRTPStreamStats.Value();
        }
      }
      // Then, look for the things we want telemetry on
      auto& array = r.mInboundRTPStreamStats.Value();
      for (decltype(array.Length()) i = 0; i < array.Length(); i++) {
        auto& s = array[i];
        bool isAudio = (s.mId.Value().Find("audio") != -1);
        if (s.mPacketsLost.WasPassed() && s.mPacketsReceived.WasPassed() &&
            (s.mPacketsLost.Value() + s.mPacketsReceived.Value()) != 0) {
          ID id;
          if (s.mIsRemote) {
            id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS_RATE :
                           WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS_RATE;
          } else {
            id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS_RATE :
                           WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS_RATE;
          }
          // *1000 so we can read in 10's of a percent (permille)
          Accumulate(id,
                     (s.mPacketsLost.Value() * 1000) /
                     (s.mPacketsLost.Value() + s.mPacketsReceived.Value()));
        }
        if (s.mJitter.WasPassed()) {
          ID id;
          if (s.mIsRemote) {
            id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER :
                           WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER;
          } else {
            id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER :
                           WEBRTC_VIDEO_QUALITY_INBOUND_JITTER;
          }
          Accumulate(id, s.mJitter.Value());
        }
        if (s.mMozRtt.WasPassed()) {
          MOZ_ASSERT(s.mIsRemote);
          ID id;
          if (isAudio) {
            id = isHello ? LOOP_AUDIO_QUALITY_OUTBOUND_RTT :
                           WEBRTC_AUDIO_QUALITY_OUTBOUND_RTT;
          } else {
            id = isHello ? LOOP_VIDEO_QUALITY_OUTBOUND_RTT :
                           WEBRTC_VIDEO_QUALITY_OUTBOUND_RTT;
          }
          Accumulate(id, s.mMozRtt.Value());
        }
        if (lastInboundStats && s.mBytesReceived.WasPassed()) {
          auto& laststats = *lastInboundStats;
          auto i = FindId(laststats, s.mId.Value());
          if (i != laststats.NoIndex) {
            auto& lasts = laststats[i];
            if (lasts.mBytesReceived.WasPassed()) {
              auto delta_ms = int32_t(s.mTimestamp.Value() -
                                      lasts.mTimestamp.Value());
              // In theory we're called every second, so delta *should* be in that range.
              // Small deltas could cause errors due to division
              if (delta_ms > 500 && delta_ms < 60000) {
                ID id;
                if (s.mIsRemote) {
                  if (isAudio) {
                    id = isHello ? LOOP_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS :
                                   WEBRTC_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS;
                  } else {
                    id = isHello ? LOOP_VIDEO_QUALITY_OUTBOUND_BANDWIDTH_KBITS :
                                   WEBRTC_VIDEO_QUALITY_OUTBOUND_BANDWIDTH_KBITS;
                  }
                } else {
                  if (isAudio) {
                    id = isHello ? LOOP_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS :
                                   WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS;
                  } else {
                    id = isHello ? LOOP_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS :
                                   WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS;
                  }
                }
                Accumulate(id, ((s.mBytesReceived.Value() -
                                 lasts.mBytesReceived.Value()) * 8) / delta_ms);
              }
              // We could accumulate values until enough time has passed
              // and then Accumulate() but this isn't that important.
            }
          }
        }
      }
    }
  }
  // Steal and hang on to reports for the next second
  ctx->mLastReports.Clear();
  for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) {
    ctx->mLastReports.AppendElement((*q)->report.forget()); // steal avoids copy
  }
  // Container must be freed back on main thread
  NS_DispatchToMainThread(WrapRunnableNM(&FreeOnMain_m, aQueryList),
                          NS_DISPATCH_NORMAL);
}