1
0
Fork 0
firefox/toolkit/components/glean/bindings/private/Ping.cpp
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

219 lines
7.7 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "mozilla/glean/bindings/Ping.h"
#include "jsapi.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Components.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "nsIClassInfoImpl.h"
#include "nsTHashMap.h"
#include "nsString.h"
#include <memory>
namespace mozilla::glean {
namespace impl {
using CallbackMapType = nsTHashMap<uint32_t, FalliblePingTestCallback>;
using MetricIdToCallbackMutex = StaticDataMutex<UniquePtr<CallbackMapType>>;
static Maybe<MetricIdToCallbackMutex::AutoLock> GetCallbackMapLock() {
static MetricIdToCallbackMutex sCallbacks("sCallbacks");
auto lock = sCallbacks.Lock();
// Test callbacks will continue to work until the end of AppShutdownTelemetry
if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
return Nothing();
}
if (!*lock) {
*lock = MakeUnique<CallbackMapType>();
RunOnShutdown(
[&] {
auto lock = sCallbacks.Lock();
*lock = nullptr; // deletes, see UniquePtr.h
},
ShutdownPhase::XPCOMWillShutdown);
}
return Some(std::move(lock));
}
void Ping::Submit(const nsACString& aReason) const {
(void)SubmitInternal(aReason);
}
nsresult Ping::SubmitInternal(const nsACString& aReason) const {
nsresult rv = NS_OK;
{
auto callback = Maybe<FalliblePingTestCallback>();
GetCallbackMapLock().apply(
[&](const auto& lock) { callback = lock.ref()->Extract(mId); });
// Calling the callback outside of the lock allows it to register a new
// callback itself.
if (callback) {
rv = callback.extract()(aReason);
}
}
fog_submit_ping_by_id(mId, &aReason);
return rv;
}
void Ping::SetEnabled(bool aValue) const {
fog_set_ping_enabled_by_id(mId, aValue);
}
void Ping::TestBeforeNextSubmit(PingTestCallback&& aCallback) const {
TestBeforeNextSubmitFallible(
[callback = std::move(aCallback)](const nsACString& aReason) -> nsresult {
callback(aReason);
return NS_OK;
});
}
void Ping::TestBeforeNextSubmitFallible(
FalliblePingTestCallback&& aCallback) const {
{
GetCallbackMapLock().apply(
[&](const auto& lock) { lock.ref()->InsertOrUpdate(mId, aCallback); });
}
}
bool Ping::TestSubmission(PingTestCallback&& aTestCallback,
PingSubmitCallback&& aSubmitCallback) const {
// We could probably get away with passing a reference to the callbacks, but
// if aSubmitCallback does not actually submit the ping, then we will have
// created a reference to stack memory that will persist after this function
// returns.
auto didCall = std::make_shared<bool>(false);
TestBeforeNextSubmit([callback = std::move(aTestCallback),
didCall](const nsACString& aReason) {
*didCall = true;
callback(aReason);
});
aSubmitCallback();
return *didCall;
}
} // namespace impl
NS_IMPL_CLASSINFO(GleanPing, nullptr, 0, {0})
NS_IMPL_ISUPPORTS_CI(GleanPing, nsIGleanPing)
NS_IMETHODIMP
GleanPing::Submit(const nsACString& aReason) {
return mPing.SubmitInternal(aReason);
}
NS_IMETHODIMP
GleanPing::SetEnabled(bool aValue) {
mPing.SetEnabled(aValue);
return NS_OK;
}
NS_IMETHODIMP
GleanPing::TestBeforeNextSubmit(nsIGleanPingTestCallback* aCallback) {
if (NS_WARN_IF(!aCallback)) {
return NS_ERROR_INVALID_ARG;
}
// Throw the bare ptr into a COM ptr to keep it around in the lambda.
mPing.TestBeforeNextSubmitFallible(
[callback = nsCOMPtr(aCallback)](const nsACString& aReason) -> nsresult {
return callback->Call(aReason);
});
return NS_OK;
}
NS_IMETHODIMP
GleanPing::TestSubmission(nsIGleanPingTestCallback* aTestCallback,
nsIGleanPingSubmitCallback* aSubmitCallback,
uint32_t aSubmitTimeoutMs, JSContext* aCx,
dom::Promise** aOutPromise) {
if (NS_WARN_IF(!aTestCallback || !aSubmitCallback)) {
return NS_ERROR_INVALID_ARG;
}
IgnoredErrorResult err;
// This promise will be resolved if `aTestCallback` is called (i.e., the ping
// is submitted) and doesn't throw.
RefPtr<dom::Promise> submittedPromise =
dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), err);
if (err.Failed()) {
return err.StealNSResult();
}
// Wrap the callback with one that will resolve or reject `submittedPromise`.
mPing.TestBeforeNextSubmit([testCallback = nsCOMPtr{aTestCallback},
submittedPromise](const nsACString& aReason) {
nsresult rv = testCallback->Call(aReason);
if (NS_SUCCEEDED(rv)) {
submittedPromise->MaybeResolveWithUndefined();
} else {
// If the callback threw we need to pass that along as a promise
// rejection.
submittedPromise->MaybeReject(rv);
}
});
// Call `aSubmitCallback` to trigger ping submission. This function may or may
// not be async, but becuase it *can* be, XPConnect will always promise wrap
// its return value.
RefPtr<dom::Promise> submitPromise;
MOZ_TRY(aSubmitCallback->Call(getter_AddRefs(submitPromise)));
RefPtr<dom::Promise> thenPromise;
MOZ_TRY_VAR(thenPromise,
submitPromise->ThenWithCycleCollectedArgs(
[aSubmitTimeoutMs](JSContext*, JS::Handle<JS::Value>,
ErrorResult& aRv,
RefPtr<dom::Promise> aSubmittedPromise)
-> already_AddRefed<dom::Promise> {
// The submit callback has finished successfully now
// (whether or not it was async).
if (aSubmitTimeoutMs) {
// We have a submit timeout, which means that
// `aSubmitCallback` has triggered the submission through
// some async means that does not complete before it
// returns (e.g., idle dispatch from c++). Reject
// `submittedPromise` after the given timeout to make sure
// we don't hang forever waiting for a submission that
// might not happen.
nsresult rv = NS_DelayedDispatchToCurrentThread(
NS_NewRunnableFunction(
__func__,
[submittedPromise = RefPtr(aSubmittedPromise)]() {
submittedPromise->MaybeRejectWithTimeoutError(
"Ping was not submitted after timeout");
}),
aSubmitTimeoutMs);
if (NS_FAILED(rv)) {
aSubmittedPromise->MaybeReject(rv);
}
} else {
// If there is no timeout then the ping should have been
// submitted already. We attempt to reject the promise,
// which will have no affect if the promise is already
// settled.
aSubmittedPromise->MaybeRejectWithOperationError(
"Ping did not submit immediately");
}
// Chain into `submittedPromise` so that we will
// resolve/reject appropriately.
return aSubmittedPromise.forget();
},
std::move(submittedPromise)));
thenPromise.forget(aOutPromise);
return NS_OK;
}
} // namespace mozilla::glean