summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/ipc/FOGIPC.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/glean/ipc/FOGIPC.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/glean/ipc/FOGIPC.cpp')
-rw-r--r--toolkit/components/glean/ipc/FOGIPC.cpp575
1 files changed, 575 insertions, 0 deletions
diff --git a/toolkit/components/glean/ipc/FOGIPC.cpp b/toolkit/components/glean/ipc/FOGIPC.cpp
new file mode 100644
index 0000000000..e44cf3e863
--- /dev/null
+++ b/toolkit/components/glean/ipc/FOGIPC.cpp
@@ -0,0 +1,575 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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 "FOGIPC.h"
+
+#include <limits>
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/gfx/GPUChild.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/Hal.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/ProcInfo.h"
+#include "mozilla/RDDChild.h"
+#include "mozilla/RDDParent.h"
+#include "mozilla/RDDProcessManager.h"
+#include "mozilla/ipc/UtilityProcessChild.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "mozilla/ipc/UtilityProcessParent.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "mozilla/Unused.h"
+#include "GMPPlatform.h"
+#include "GMPServiceParent.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIXULRuntime.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+using mozilla::dom::ContentParent;
+using mozilla::gfx::GPUChild;
+using mozilla::gfx::GPUProcessManager;
+using mozilla::ipc::ByteBuf;
+using mozilla::ipc::UtilityProcessChild;
+using mozilla::ipc::UtilityProcessManager;
+using mozilla::ipc::UtilityProcessParent;
+using FlushFOGDataPromise = mozilla::dom::ContentParent::FlushFOGDataPromise;
+
+namespace geckoprofiler::markers {
+
+using namespace mozilla;
+
+struct ProcessingTimeMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("ProcessingTime");
+ }
+ static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
+ int64_t aDiffMs,
+ const ProfilerString8View& aType,
+ const ProfilerString8View& aTrackerType) {
+ aWriter.IntProperty("time", aDiffMs);
+ aWriter.StringProperty("label", aType);
+ if (aTrackerType.Length() > 0) {
+ aWriter.StringProperty("tracker", aTrackerType);
+ }
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
+ schema.AddKeyLabelFormat("time", "Recorded Time", MS::Format::Milliseconds);
+ schema.AddKeyLabelFormat("tracker", "Tracker Type", MS::Format::String);
+ schema.SetTooltipLabel("{marker.name} - {marker.data.label}");
+ schema.SetTableLabel(
+ "{marker.name} - {marker.data.label}: {marker.data.time}");
+ return schema;
+ }
+};
+
+} // namespace geckoprofiler::markers
+
+namespace mozilla::glean {
+
+// Echoes processtools/metrics.yaml's power.wakeups_per_thread
+enum ProcessType {
+ eParentActive,
+ eParentInactive,
+ eContentForeground,
+ eContentBackground,
+ eGpuProcess,
+ eUnknown,
+};
+
+// This static global is set within RecordPowerMetrics on the main thread,
+// using information only gettable on the main thread, and is read within
+// RecordThreadCpuUse on any thread.
+static Atomic<ProcessType> gThisProcessType(eUnknown);
+
+#ifdef NIGHTLY_BUILD
+// It is fine to call RecordThreadCpuUse during startup before the first
+// RecordPowerMetrics call. In that case the parent process will be recorded
+// as inactive, and other processes will be ignored (content processes start
+// in the 'prealloc' type for which we don't record per-thread CPU use data).
+
+void RecordThreadCpuUse(const nsACString& aThreadName, uint64_t aCpuTimeMs,
+ uint64_t aWakeCount) {
+ ProcessType processType = gThisProcessType;
+
+ if (processType == ProcessType::eUnknown) {
+ if (XRE_IsParentProcess()) {
+ // During startup we might not have gotten a RecordPowerMetrics call.
+ // That's fine. Default to eParentInactive.
+ processType = eParentInactive;
+ } else {
+ // We are not interested in per-thread CPU use data for the current
+ // process type.
+ return;
+ }
+ }
+
+ nsAutoCString threadName(aThreadName);
+ for (size_t i = 0; i < threadName.Length(); ++i) {
+ const char c = threadName.CharAt(i);
+
+ // Valid characters.
+ if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '-' ||
+ c == '_') {
+ continue;
+ }
+
+ // Should only use lower case characters
+ if (c >= 'A' && c <= 'Z') {
+ threadName.SetCharAt(c + ('a' - 'A'), i);
+ continue;
+ }
+
+ // Replace everything else with _
+ threadName.SetCharAt('_', i);
+ }
+
+ if (aCpuTimeMs != 0 &&
+ MOZ_LIKELY(aCpuTimeMs < std::numeric_limits<int32_t>::max())) {
+ switch (processType) {
+ case eParentActive:
+ power_cpu_ms_per_thread::parent_active.Get(threadName)
+ .Add(int32_t(aCpuTimeMs));
+ break;
+ case eParentInactive:
+ power_cpu_ms_per_thread::parent_inactive.Get(threadName)
+ .Add(int32_t(aCpuTimeMs));
+ break;
+ case eContentForeground:
+ power_cpu_ms_per_thread::content_foreground.Get(threadName)
+ .Add(int32_t(aCpuTimeMs));
+ break;
+ case eContentBackground:
+ power_cpu_ms_per_thread::content_background.Get(threadName)
+ .Add(int32_t(aCpuTimeMs));
+ break;
+ case eGpuProcess:
+ power_cpu_ms_per_thread::gpu_process.Get(threadName)
+ .Add(int32_t(aCpuTimeMs));
+ break;
+ case eUnknown:
+ // Nothing to do.
+ break;
+ }
+ }
+
+ if (aWakeCount != 0 &&
+ MOZ_LIKELY(aWakeCount < std::numeric_limits<int32_t>::max())) {
+ switch (processType) {
+ case eParentActive:
+ power_wakeups_per_thread::parent_active.Get(threadName)
+ .Add(int32_t(aWakeCount));
+ break;
+ case eParentInactive:
+ power_wakeups_per_thread::parent_inactive.Get(threadName)
+ .Add(int32_t(aWakeCount));
+ break;
+ case eContentForeground:
+ power_wakeups_per_thread::content_foreground.Get(threadName)
+ .Add(int32_t(aWakeCount));
+ break;
+ case eContentBackground:
+ power_wakeups_per_thread::content_background.Get(threadName)
+ .Add(int32_t(aWakeCount));
+ break;
+ case eGpuProcess:
+ power_wakeups_per_thread::gpu_process.Get(threadName)
+ .Add(int32_t(aWakeCount));
+ break;
+ case eUnknown:
+ // Nothing to do.
+ break;
+ }
+ }
+}
+#endif
+
+void GetTrackerType(nsAutoCString& aTrackerType) {
+ using namespace mozilla::dom;
+ uint32_t trackingFlags =
+ (nsIClassifiedChannel::CLASSIFIED_CRYPTOMINING |
+ nsIClassifiedChannel::CLASSIFIED_FINGERPRINTING |
+ nsIClassifiedChannel::CLASSIFIED_TRACKING |
+ nsIClassifiedChannel::CLASSIFIED_TRACKING_AD |
+ nsIClassifiedChannel::CLASSIFIED_TRACKING_ANALYTICS |
+ nsIClassifiedChannel::CLASSIFIED_TRACKING_SOCIAL);
+ AutoTArray<RefPtr<BrowsingContextGroup>, 5> bcGroups;
+ BrowsingContextGroup::GetAllGroups(bcGroups);
+ for (auto& bcGroup : bcGroups) {
+ AutoTArray<DocGroup*, 5> docGroups;
+ bcGroup->GetDocGroups(docGroups);
+ for (auto* docGroup : docGroups) {
+ for (Document* doc : *docGroup) {
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+ do_QueryInterface(doc->GetChannel());
+ if (classifiedChannel) {
+ uint32_t classificationFlags =
+ classifiedChannel->GetThirdPartyClassificationFlags();
+ trackingFlags &= classificationFlags;
+ if (!trackingFlags) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // The if-elseif-else chain works because the tracker types listed here are
+ // currently mutually exclusive and should be maintained that way by policy.
+ if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_TRACKING_AD) {
+ aTrackerType = "ad";
+ } else if (trackingFlags ==
+ nsIClassifiedChannel::CLASSIFIED_TRACKING_ANALYTICS) {
+ aTrackerType = "analytics";
+ } else if (trackingFlags ==
+ nsIClassifiedChannel::CLASSIFIED_TRACKING_SOCIAL) {
+ aTrackerType = "social";
+ } else if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_CRYPTOMINING) {
+ aTrackerType = "cryptomining";
+ } else if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_FINGERPRINTING) {
+ aTrackerType = "fingerprinting";
+ } else if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_TRACKING) {
+ // CLASSIFIED_TRACKING means we were not able to identify the type of
+ // classification.
+ aTrackerType = "unknown";
+ }
+}
+
+void RecordPowerMetrics() {
+ static uint64_t previousCpuTime = 0, previousGpuTime = 0;
+
+ uint64_t cpuTime, newCpuTime = 0;
+ if (NS_SUCCEEDED(GetCpuTimeSinceProcessStartInMs(&cpuTime)) &&
+ cpuTime > previousCpuTime) {
+ newCpuTime = cpuTime - previousCpuTime;
+ }
+
+ uint64_t gpuTime, newGpuTime = 0;
+ // Avoid loading gdi32.dll for the Socket process where the GPU is never used.
+ if (!XRE_IsSocketProcess() &&
+ NS_SUCCEEDED(GetGpuTimeSinceProcessStartInMs(&gpuTime)) &&
+ gpuTime > previousGpuTime) {
+ newGpuTime = gpuTime - previousGpuTime;
+ }
+
+ if (!newCpuTime && !newGpuTime) {
+ // Nothing to record.
+ return;
+ }
+
+ // Compute the process type string.
+ nsAutoCString type(XRE_GetProcessTypeString());
+ nsAutoCString trackerType;
+ if (XRE_IsContentProcess()) {
+ auto* cc = dom::ContentChild::GetSingleton();
+ if (cc) {
+ type.Assign(dom::RemoteTypePrefix(cc->GetRemoteType()));
+ if (StringBeginsWith(type, WEB_REMOTE_TYPE)) {
+ type.AssignLiteral("web");
+ switch (cc->GetProcessPriority()) {
+ case hal::PROCESS_PRIORITY_BACKGROUND:
+ type.AppendLiteral(".background");
+ gThisProcessType = ProcessType::eContentBackground;
+ break;
+ case hal::PROCESS_PRIORITY_FOREGROUND:
+ type.AppendLiteral(".foreground");
+ gThisProcessType = ProcessType::eContentForeground;
+ break;
+ case hal::PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE:
+ type.AppendLiteral(".background-perceivable");
+ gThisProcessType = ProcessType::eUnknown;
+ break;
+ default:
+ gThisProcessType = ProcessType::eUnknown;
+ break;
+ }
+ }
+ GetTrackerType(trackerType);
+ } else {
+ gThisProcessType = ProcessType::eUnknown;
+ }
+ } else if (XRE_IsParentProcess()) {
+ if (nsContentUtils::GetUserIsInteracting()) {
+ type.AssignLiteral("parent.active");
+ gThisProcessType = ProcessType::eParentActive;
+ } else {
+ type.AssignLiteral("parent.inactive");
+ gThisProcessType = ProcessType::eParentInactive;
+ }
+ hal::WakeLockInformation info;
+ GetWakeLockInfo(u"video-playing"_ns, &info);
+ if (info.numLocks() != 0 && info.numHidden() < info.numLocks()) {
+ type.AppendLiteral(".playing-video");
+ } else {
+ GetWakeLockInfo(u"audio-playing"_ns, &info);
+ if (info.numLocks()) {
+ type.AppendLiteral(".playing-audio");
+ }
+ }
+ } else if (XRE_IsGPUProcess()) {
+ gThisProcessType = ProcessType::eGpuProcess;
+ } else {
+ gThisProcessType = ProcessType::eUnknown;
+ }
+
+ if (newCpuTime) {
+ // The counters are reset at least once a day. Assuming all cores are used
+ // continuously, an int32 can hold the data for 24.85 cores.
+ // This should be fine for now, but may overflow in the future.
+ // Bug 1751277 tracks a newer, bigger counter.
+ int32_t nNewCpuTime = int32_t(newCpuTime);
+ if (newCpuTime < std::numeric_limits<int32_t>::max()) {
+ power::total_cpu_time_ms.Add(nNewCpuTime);
+ power::cpu_time_per_process_type_ms.Get(type).Add(nNewCpuTime);
+ if (!trackerType.IsEmpty()) {
+ power::cpu_time_per_tracker_type_ms.Get(trackerType).Add(nNewCpuTime);
+ }
+ } else {
+ power::cpu_time_bogus_values.Add(1);
+ }
+ PROFILER_MARKER("Process CPU Time", OTHER, {}, ProcessingTimeMarker,
+ nNewCpuTime, type, trackerType);
+ previousCpuTime += newCpuTime;
+ }
+
+ if (newGpuTime) {
+ int32_t nNewGpuTime = int32_t(newGpuTime);
+ if (newGpuTime < std::numeric_limits<int32_t>::max()) {
+ power::total_gpu_time_ms.Add(nNewGpuTime);
+ power::gpu_time_per_process_type_ms.Get(type).Add(nNewGpuTime);
+ } else {
+ power::gpu_time_bogus_values.Add(1);
+ }
+ PROFILER_MARKER("Process GPU Time", OTHER, {}, ProcessingTimeMarker,
+ nNewGpuTime, type, trackerType);
+ previousGpuTime += newGpuTime;
+ }
+
+ profiler_record_wakeup_count(type);
+}
+
+/**
+ * Flush your data ASAP, either because the parent process is asking you to
+ * or because the process is about to shutdown.
+ *
+ * @param aResolver - The function you need to call with the bincoded,
+ * serialized payload that the Rust impl hands you.
+ */
+void FlushFOGData(std::function<void(ipc::ByteBuf&&)>&& aResolver) {
+ // Record power metrics right before data is sent to the parent.
+ RecordPowerMetrics();
+
+ ByteBuf buf;
+ uint32_t ipcBufferSize = impl::fog_serialize_ipc_buf();
+ bool ok = buf.Allocate(ipcBufferSize);
+ if (!ok) {
+ return;
+ }
+ uint32_t writtenLen = impl::fog_give_ipc_buf(buf.mData, buf.mLen);
+ if (writtenLen != ipcBufferSize) {
+ return;
+ }
+ aResolver(std::move(buf));
+}
+
+/**
+ * Called by FOG on the parent process when it wants to flush all its
+ * children's data.
+ * @param aResolver - The function that'll be called with the results.
+ */
+void FlushAllChildData(
+ std::function<void(nsTArray<ipc::ByteBuf>&&)>&& aResolver) {
+ auto timerId = fog_ipc::flush_durations.Start();
+
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ nsTArray<RefPtr<FlushFOGDataPromise>> promises;
+ for (auto* parent : parents) {
+ promises.EmplaceBack(parent->SendFlushFOGData());
+ }
+
+ if (GPUProcessManager* gpuManager = GPUProcessManager::Get()) {
+ if (GPUChild* gpuChild = gpuManager->GetGPUChild()) {
+ promises.EmplaceBack(gpuChild->SendFlushFOGData());
+ }
+ }
+
+ if (RDDProcessManager* rddManager = RDDProcessManager::Get()) {
+ if (RDDChild* rddChild = rddManager->GetRDDChild()) {
+ promises.EmplaceBack(rddChild->SendFlushFOGData());
+ }
+ }
+
+ if (net::SocketProcessParent* socketParent =
+ net::SocketProcessParent::GetSingleton()) {
+ promises.EmplaceBack(socketParent->SendFlushFOGData());
+ }
+
+ if (RefPtr<mozilla::gmp::GeckoMediaPluginServiceParent> gmps =
+ mozilla::gmp::GeckoMediaPluginServiceParent::GetSingleton()) {
+ // There can be multiple Gecko Media Plugin processes, but iterating
+ // through them requires locking a mutex and the IPCs need to be sent
+ // from a different thread, so it's better to let the
+ // GeckoMediaPluginServiceParent code do it for us.
+ gmps->SendFlushFOGData(promises);
+ }
+
+ if (RefPtr<UtilityProcessManager> utilityManager =
+ UtilityProcessManager::GetIfExists()) {
+ for (RefPtr<UtilityProcessParent>& parent :
+ utilityManager->GetAllProcessesProcessParent()) {
+ promises.EmplaceBack(parent->SendFlushFOGData());
+ }
+ }
+
+ if (promises.Length() == 0) {
+ // No child processes at the moment. Resolve synchronously.
+ fog_ipc::flush_durations.Cancel(std::move(timerId));
+ nsTArray<ipc::ByteBuf> results;
+ aResolver(std::move(results));
+ return;
+ }
+
+ // If fog.ipc.flush_failures ever gets too high:
+ // TODO: Don't throw away resolved data if some of the promises reject.
+ // (not sure how, but it'll mean not using ::All... maybe a custom copy of
+ // AllPromiseHolder? Might be impossible outside MozPromise.h)
+ FlushFOGDataPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [aResolver = std::move(aResolver), timerId](
+ FlushFOGDataPromise::AllPromiseType::ResolveOrRejectValue&&
+ aValue) {
+ fog_ipc::flush_durations.StopAndAccumulate(std::move(timerId));
+ if (aValue.IsResolve()) {
+ aResolver(std::move(aValue.ResolveValue()));
+ } else {
+ fog_ipc::flush_failures.Add(1);
+ nsTArray<ipc::ByteBuf> results;
+ aResolver(std::move(results));
+ }
+ });
+}
+
+/**
+ * A child process has sent you this buf as a treat.
+ * @param buf - a bincoded serialized payload that the Rust impl understands.
+ */
+void FOGData(ipc::ByteBuf&& buf) {
+ JOG::EnsureRuntimeMetricsRegistered();
+ fog_ipc::buffer_sizes.Accumulate(buf.mLen);
+ impl::fog_use_ipc_buf(buf.mData, buf.mLen);
+}
+
+/**
+ * Called by FOG on a child process when it wants to send a buf to the parent.
+ * @param buf - a bincoded serialized payload that the Rust impl understands.
+ */
+void SendFOGData(ipc::ByteBuf&& buf) {
+ switch (XRE_GetProcessType()) {
+ case GeckoProcessType_Content:
+ mozilla::dom::ContentChild::GetSingleton()->SendFOGData(std::move(buf));
+ break;
+ case GeckoProcessType_GMPlugin: {
+ mozilla::gmp::SendFOGData(std::move(buf));
+ } break;
+ case GeckoProcessType_GPU:
+ Unused << mozilla::gfx::GPUParent::GetSingleton()->SendFOGData(
+ std::move(buf));
+ break;
+ case GeckoProcessType_RDD:
+ Unused << mozilla::RDDParent::GetSingleton()->SendFOGData(std::move(buf));
+ break;
+ case GeckoProcessType_Socket:
+ Unused << net::SocketProcessChild::GetSingleton()->SendFOGData(
+ std::move(buf));
+ break;
+ case GeckoProcessType_Utility:
+ Unused << ipc::UtilityProcessChild::GetSingleton()->SendFOGData(
+ std::move(buf));
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsuppored process type");
+ }
+}
+
+/**
+ * Called on the parent process to ask all child processes for data,
+ * sending it all down into Rust to be used.
+ */
+RefPtr<GenericPromise> FlushAndUseFOGData() {
+ // Record power metrics on the parent before sending requests to child
+ // processes.
+ RecordPowerMetrics();
+
+ RefPtr<GenericPromise::Private> ret = new GenericPromise::Private(__func__);
+ std::function<void(nsTArray<ByteBuf>&&)> resolver =
+ [ret](nsTArray<ByteBuf>&& bufs) {
+ for (ByteBuf& buf : bufs) {
+ FOGData(std::move(buf));
+ }
+ ret->Resolve(true, __func__);
+ };
+ FlushAllChildData(std::move(resolver));
+ return ret;
+}
+
+void TestTriggerMetrics(uint32_t aProcessType,
+ const RefPtr<dom::Promise>& promise) {
+ switch (aProcessType) {
+ case nsIXULRuntime::PROCESS_TYPE_GMPLUGIN: {
+ RefPtr<mozilla::gmp::GeckoMediaPluginServiceParent> gmps(
+ mozilla::gmp::GeckoMediaPluginServiceParent::GetSingleton());
+ gmps->TestTriggerMetrics()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise]() { promise->MaybeResolveWithUndefined(); },
+ [promise]() { promise->MaybeRejectWithUndefined(); });
+ } break;
+ case nsIXULRuntime::PROCESS_TYPE_GPU:
+ mozilla::gfx::GPUProcessManager::Get()->TestTriggerMetrics()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise]() { promise->MaybeResolveWithUndefined(); },
+ [promise]() { promise->MaybeRejectWithUndefined(); });
+ break;
+ case nsIXULRuntime::PROCESS_TYPE_RDD:
+ RDDProcessManager::Get()->TestTriggerMetrics()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise]() { promise->MaybeResolveWithUndefined(); },
+ [promise]() { promise->MaybeRejectWithUndefined(); });
+ break;
+ case nsIXULRuntime::PROCESS_TYPE_SOCKET:
+ Unused << net::SocketProcessParent::GetSingleton()
+ ->SendTestTriggerMetrics()
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise]() { promise->MaybeResolveWithUndefined(); },
+ [promise]() { promise->MaybeRejectWithUndefined(); });
+ break;
+ case nsIXULRuntime::PROCESS_TYPE_UTILITY:
+ Unused << ipc::UtilityProcessManager::GetSingleton()
+ ->GetProcessParent(ipc::SandboxingKind::GENERIC_UTILITY)
+ ->SendTestTriggerMetrics()
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise]() { promise->MaybeResolveWithUndefined(); },
+ [promise]() { promise->MaybeRejectWithUndefined(); });
+ break;
+ default:
+ promise->MaybeRejectWithUndefined();
+ break;
+ }
+}
+
+} // namespace mozilla::glean