LCOV - code coverage report
Current view: top level - toolkit/components/telemetry - TelemetryScalar.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 110 1317 8.4 %
Date: 2018-08-07 16:35:00 Functions: 0 0 -
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "nsITelemetry.h"
       8             : #include "nsIVariant.h"
       9             : #include "nsVariant.h"
      10             : #include "nsHashKeys.h"
      11             : #include "nsBaseHashtable.h"
      12             : #include "nsClassHashtable.h"
      13             : #include "nsDataHashtable.h"
      14             : #include "nsIXPConnect.h"
      15             : #include "nsContentUtils.h"
      16             : #include "nsThreadUtils.h"
      17             : #include "nsJSUtils.h"
      18             : #include "nsPrintfCString.h"
      19             : #include "mozilla/dom/ContentParent.h"
      20             : #include "mozilla/dom/PContent.h"
      21             : #include "mozilla/JSONWriter.h"
      22             : #include "mozilla/Preferences.h"
      23             : #include "mozilla/StaticMutex.h"
      24             : #include "mozilla/StaticPtr.h"
      25             : #include "mozilla/Unused.h"
      26             : 
      27             : #include "TelemetryCommon.h"
      28             : #include "TelemetryScalar.h"
      29             : #include "TelemetryScalarData.h"
      30             : #include "ipc/TelemetryComms.h"
      31             : #include "ipc/TelemetryIPCAccumulator.h"
      32             : 
      33             : using mozilla::Preferences;
      34             : using mozilla::StaticAutoPtr;
      35             : using mozilla::StaticMutex;
      36             : using mozilla::StaticMutexAutoLock;
      37             : using mozilla::Some;
      38             : using mozilla::Nothing;
      39             : using mozilla::Telemetry::Common::AutoHashtable;
      40             : using mozilla::Telemetry::Common::IsExpiredVersion;
      41             : using mozilla::Telemetry::Common::CanRecordDataset;
      42             : using mozilla::Telemetry::Common::CanRecordProduct;
      43             : using mozilla::Telemetry::Common::IsInDataset;
      44             : using mozilla::Telemetry::Common::LogToBrowserConsole;
      45             : using mozilla::Telemetry::Common::GetNameForProcessID;
      46             : using mozilla::Telemetry::Common::GetIDForProcessName;
      47             : using mozilla::Telemetry::Common::RecordedProcessType;
      48             : using mozilla::Telemetry::Common::IsValidIdentifierString;
      49             : using mozilla::Telemetry::Common::GetCurrentProduct;
      50             : using mozilla::Telemetry::Common::SupportedProduct;
      51             : using mozilla::Telemetry::ScalarActionType;
      52             : using mozilla::Telemetry::ScalarAction;
      53             : using mozilla::Telemetry::KeyedScalarAction;
      54             : using mozilla::Telemetry::ScalarID;
      55             : using mozilla::Telemetry::DynamicScalarDefinition;
      56             : using mozilla::Telemetry::ScalarVariant;
      57             : using mozilla::Telemetry::ProcessID;
      58             : 
      59             : namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
      60             : 
      61             : ////////////////////////////////////////////////////////////////////////
      62             : ////////////////////////////////////////////////////////////////////////
      63             : //
      64             : // Naming: there are two kinds of functions in this file:
      65             : //
      66             : // * Functions named internal_*: these can only be reached via an
      67             : //   interface function (TelemetryScalar::*). If they access shared
      68             : //   state, they require the interface function to have acquired
      69             : //   |gTelemetryScalarMutex| to ensure thread safety.
      70             : //
      71             : // * Functions named TelemetryScalar::*. This is the external interface.
      72             : //   Entries and exits to these functions are serialised using
      73             : //   |gTelemetryScalarsMutex|.
      74             : //
      75             : // Avoiding races and deadlocks:
      76             : //
      77             : // All functions in the external interface (TelemetryScalar::*) are
      78             : // serialised using the mutex |gTelemetryScalarsMutex|. This means
      79             : // that the external interface is thread-safe. But it also brings
      80             : // a danger of deadlock if any function in the external interface can
      81             : // get back to that interface. That is, we will deadlock on any call
      82             : // chain like this
      83             : //
      84             : // TelemetryScalar::* -> .. any functions .. -> TelemetryScalar::*
      85             : //
      86             : // To reduce the danger of that happening, observe the following rules:
      87             : //
      88             : // * No function in TelemetryScalar::* may directly call, nor take the
      89             : //   address of, any other function in TelemetryScalar::*.
      90             : //
      91             : // * No internal function internal_* may call, nor take the address
      92             : //   of, any function in TelemetryScalar::*.
      93             : 
      94             : ////////////////////////////////////////////////////////////////////////
      95             : ////////////////////////////////////////////////////////////////////////
      96             : //
      97             : // PRIVATE TYPES
      98             : 
      99             : namespace {
     100             : 
     101             : const uint32_t kMaximumNumberOfKeys = 100;
     102             : const uint32_t kMaximumKeyStringLength = 72;
     103             : const uint32_t kMaximumStringValueLength = 50;
     104             : // The category and scalar name maximum lengths are used by the dynamic
     105             : // scalar registration function and must match the constants used by
     106             : // the 'parse_scalars.py' script for static scalars.
     107             : const uint32_t kMaximumCategoryNameLength = 40;
     108             : const uint32_t kMaximumScalarNameLength = 40;
     109             : const uint32_t kScalarCount =
     110             :   static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
     111             : 
     112             : // To stop growing unbounded in memory while waiting for scalar deserialization
     113             : // to finish, we immediately apply pending operations if the array reaches
     114             : // a certain high water mark of elements.
     115             : const size_t kScalarActionsArrayHighWaterMark = 10000;
     116             : 
     117             : enum class ScalarResult : uint8_t {
     118             :   // Nothing went wrong.
     119             :   Ok,
     120             :   // General Scalar Errors
     121             :   NotInitialized,
     122             :   CannotUnpackVariant,
     123             :   CannotRecordInProcess,
     124             :   CannotRecordDataset,
     125             :   KeyedTypeMismatch,
     126             :   UnknownScalar,
     127             :   OperationNotSupported,
     128             :   InvalidType,
     129             :   InvalidValue,
     130             :   // Keyed Scalar Errors
     131             :   KeyIsEmpty,
     132             :   KeyTooLong,
     133             :   TooManyKeys,
     134             :   // String Scalar Errors
     135             :   StringTooLong,
     136             :   // Unsigned Scalar Errors
     137             :   UnsignedNegativeValue,
     138             :   UnsignedTruncatedValue,
     139             : };
     140             : 
     141             : // A common identifier for both built-in and dynamic scalars.
     142             : struct ScalarKey {
     143             :   uint32_t id;
     144             :   bool dynamic;
     145             : };
     146             : 
     147             : /**
     148             :  * Scalar information for dynamic definitions.
     149             :  */
     150           0 : struct DynamicScalarInfo : BaseScalarInfo {
     151             :   nsCString mDynamicName;
     152             :   bool mDynamicExpiration;
     153             : 
     154           0 :   DynamicScalarInfo(uint32_t aKind, bool aRecordOnRelease,
     155             :                     bool aExpired, const nsACString& aName,
     156             :                     bool aKeyed, bool aBuiltin)
     157           0 :     : BaseScalarInfo(aKind,
     158             :                      aRecordOnRelease ?
     159             :                      nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT :
     160             :                      nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
     161             :                      RecordedProcessType::All,
     162             :                      aKeyed,
     163             :                      GetCurrentProduct(),
     164             :                      aBuiltin)
     165             :     , mDynamicName(aName)
     166           0 :     , mDynamicExpiration(aExpired)
     167           0 :   {}
     168             : 
     169             :   // The following functions will read the stored text
     170             :   // instead of looking it up in the statically generated
     171             :   // tables.
     172             :   const char *name() const override;
     173             :   const char *expiration() const override;
     174             : };
     175             : 
     176             : const char *
     177           0 : DynamicScalarInfo::name() const
     178             : {
     179           0 :   return mDynamicName.get();
     180             : }
     181             : 
     182             : const char *
     183           0 : DynamicScalarInfo::expiration() const
     184             : {
     185             :   // Dynamic scalars can either be expired or not (boolean flag).
     186             :   // Return an appropriate version string to leverage the scalar expiration
     187             :   // logic.
     188           0 :   return mDynamicExpiration ? "1.0" : "never";
     189             : }
     190             : 
     191             : typedef nsBaseHashtableET<nsDepCharHashKey, ScalarKey> CharPtrEntryType;
     192             : typedef AutoHashtable<CharPtrEntryType> ScalarMapType;
     193             : 
     194             : // Dynamic scalar definitions.
     195           0 : StaticAutoPtr<nsTArray<DynamicScalarInfo>> gDynamicScalarInfo;
     196             : 
     197             : const BaseScalarInfo&
     198           0 : internal_GetScalarInfo(const StaticMutexAutoLock& lock, const ScalarKey& aId)
     199             : {
     200           0 :   if (!aId.dynamic) {
     201           0 :     return gScalars[aId.id];
     202             :   }
     203             : 
     204           0 :   return (*gDynamicScalarInfo)[aId.id];
     205             : }
     206             : 
     207             : bool
     208             : IsValidEnumId(mozilla::Telemetry::ScalarID aID)
     209             : {
     210           0 :   return aID < mozilla::Telemetry::ScalarID::ScalarCount;
     211             : }
     212             : 
     213             : bool
     214           0 : internal_IsValidId(const StaticMutexAutoLock& lock, const ScalarKey& aId)
     215             : {
     216             :   // Please note that this function needs to be called with the scalar
     217             :   // mutex being acquired: other functions might be messing with
     218             :   // |gDynamicScalarInfo|.
     219           0 :   return aId.dynamic ? (aId.id < gDynamicScalarInfo->Length()) :
     220           0 :     IsValidEnumId(static_cast<mozilla::Telemetry::ScalarID>(aId.id));
     221             : }
     222             : 
     223             : /**
     224             :  * Convert a nsIVariant to a mozilla::Variant, which is used for
     225             :  * accumulating child process scalars.
     226             :  */
     227             : ScalarResult
     228           0 : GetVariantFromIVariant(nsIVariant* aInput, uint32_t aScalarKind,
     229             :                        mozilla::Maybe<ScalarVariant>& aOutput)
     230             : {
     231           0 :   switch (aScalarKind) {
     232             :     case nsITelemetry::SCALAR_TYPE_COUNT:
     233             :       {
     234           0 :         uint32_t val = 0;
     235           0 :         nsresult rv = aInput->GetAsUint32(&val);
     236           0 :         if (NS_FAILED(rv)) {
     237           0 :           return ScalarResult::CannotUnpackVariant;
     238             :         }
     239           0 :         aOutput = mozilla::Some(mozilla::AsVariant(val));
     240           0 :         break;
     241             :       }
     242             :     case nsITelemetry::SCALAR_TYPE_STRING:
     243             :       {
     244           0 :         nsString val;
     245           0 :         nsresult rv = aInput->GetAsAString(val);
     246           0 :         if (NS_FAILED(rv)) {
     247           0 :           return ScalarResult::CannotUnpackVariant;
     248             :         }
     249           0 :         aOutput = mozilla::Some(mozilla::AsVariant(val));
     250           0 :         break;
     251             :       }
     252             :     case nsITelemetry::SCALAR_TYPE_BOOLEAN:
     253             :       {
     254           0 :         bool val = false;
     255           0 :         nsresult rv = aInput->GetAsBool(&val);
     256           0 :         if (NS_FAILED(rv)) {
     257           0 :           return ScalarResult::CannotUnpackVariant;
     258             :         }
     259           0 :         aOutput = mozilla::Some(mozilla::AsVariant(val));
     260           0 :         break;
     261             :       }
     262             :     default:
     263           0 :       MOZ_ASSERT(false, "Unknown scalar kind.");
     264             :       return ScalarResult::UnknownScalar;
     265             :   }
     266             :   return ScalarResult::Ok;
     267             : }
     268             : 
     269             : /**
     270             :  * Write a nsIVariant with a JSONWriter, used for GeckoView persistence.
     271             :  */
     272             : nsresult
     273           0 : WriteVariantToJSONWriter(uint32_t aScalarType, nsIVariant* aInputValue,
     274             :                          const char* aPropertyName, mozilla::JSONWriter& aWriter)
     275             : {
     276           0 :   MOZ_ASSERT(aInputValue);
     277             : 
     278           0 :   switch (aScalarType) {
     279             :     case nsITelemetry::SCALAR_TYPE_COUNT:
     280             :       {
     281           0 :         uint32_t val = 0;
     282           0 :         nsresult rv = aInputValue->GetAsUint32(&val);
     283           0 :         NS_ENSURE_SUCCESS(rv, rv);
     284           0 :         aWriter.IntProperty(aPropertyName, val);
     285           0 :         break;
     286             :       }
     287             :     case nsITelemetry::SCALAR_TYPE_STRING:
     288             :       {
     289           0 :         nsCString val;
     290           0 :         nsresult rv = aInputValue->GetAsACString(val);
     291           0 :         NS_ENSURE_SUCCESS(rv, rv);
     292           0 :         aWriter.StringProperty(aPropertyName, val.get());
     293           0 :         break;
     294             :       }
     295             :     case nsITelemetry::SCALAR_TYPE_BOOLEAN:
     296             :       {
     297           0 :         bool val = false;
     298           0 :         nsresult rv = aInputValue->GetAsBool(&val);
     299           0 :         NS_ENSURE_SUCCESS(rv, rv);
     300           0 :         aWriter.BoolProperty(aPropertyName, val);
     301           0 :         break;
     302             :       }
     303             :     default:
     304           0 :       MOZ_ASSERT(false, "Unknown scalar kind.");
     305             :       return NS_ERROR_FAILURE;
     306             :   }
     307             : 
     308             :   return NS_OK;
     309             : }
     310             : 
     311             : // Implements the methods for ScalarInfo.
     312             : const char *
     313           0 : ScalarInfo::name() const
     314             : {
     315           0 :   return &gScalarsStringTable[this->name_offset];
     316             : }
     317             : 
     318             : const char *
     319           0 : ScalarInfo::expiration() const
     320             : {
     321           0 :   return &gScalarsStringTable[this->expiration_offset];
     322             : }
     323             : 
     324             : /**
     325             :  * The base scalar object, that serves as a common ancestor for storage
     326             :  * purposes.
     327             :  */
     328             : class ScalarBase
     329             : {
     330             : public:
     331             :   virtual ~ScalarBase() = default;
     332             : 
     333             :   // Set, Add and SetMaximum functions as described in the Telemetry IDL.
     334             :   virtual ScalarResult SetValue(nsIVariant* aValue) = 0;
     335           0 :   virtual ScalarResult AddValue(nsIVariant* aValue) { return ScalarResult::OperationNotSupported; }
     336           0 :   virtual ScalarResult SetMaximum(nsIVariant* aValue) { return ScalarResult::OperationNotSupported; }
     337             : 
     338             :   // Convenience methods used by the C++ API.
     339           0 :   virtual void SetValue(uint32_t aValue) { mozilla::Unused << HandleUnsupported(); }
     340           0 :   virtual ScalarResult SetValue(const nsAString& aValue) { return HandleUnsupported(); }
     341           0 :   virtual void SetValue(bool aValue) { mozilla::Unused << HandleUnsupported(); }
     342           0 :   virtual void AddValue(uint32_t aValue) { mozilla::Unused << HandleUnsupported(); }
     343           0 :   virtual void SetMaximum(uint32_t aValue) { mozilla::Unused << HandleUnsupported(); }
     344             : 
     345             :   // GetValue is used to get the value of the scalar when persisting it to JS.
     346             :   virtual nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const = 0;
     347             : 
     348             :   // To measure the memory stats.
     349             :   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0;
     350             : 
     351             : private:
     352             :   ScalarResult HandleUnsupported() const;
     353             : };
     354             : 
     355             : ScalarResult
     356           0 : ScalarBase::HandleUnsupported() const
     357             : {
     358           0 :   MOZ_ASSERT(false, "This operation is not support for this scalar type.");
     359             :   return ScalarResult::OperationNotSupported;
     360             : }
     361             : 
     362             : /**
     363             :  * The implementation for the unsigned int scalar type.
     364             :  */
     365             : class ScalarUnsigned : public ScalarBase
     366             : {
     367             : public:
     368             :   using ScalarBase::SetValue;
     369             : 
     370           0 :   ScalarUnsigned() : mStorage(0) {};
     371           0 :   ~ScalarUnsigned() override = default;
     372             : 
     373             :   ScalarResult SetValue(nsIVariant* aValue) final;
     374             :   void SetValue(uint32_t aValue) final;
     375             :   ScalarResult AddValue(nsIVariant* aValue) final;
     376             :   void AddValue(uint32_t aValue) final;
     377             :   ScalarResult SetMaximum(nsIVariant* aValue) final;
     378             :   void SetMaximum(uint32_t aValue) final;
     379             :   nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final;
     380             :   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
     381             : 
     382             : private:
     383             :   uint32_t mStorage;
     384             : 
     385             :   ScalarResult CheckInput(nsIVariant* aValue);
     386             : 
     387             :   // Prevent copying.
     388             :   ScalarUnsigned(const ScalarUnsigned& aOther) = delete;
     389             :   void operator=(const ScalarUnsigned& aOther) = delete;
     390             : };
     391             : 
     392             : ScalarResult
     393           0 : ScalarUnsigned::SetValue(nsIVariant* aValue)
     394             : {
     395           0 :   ScalarResult sr = CheckInput(aValue);
     396           0 :   if (sr == ScalarResult::UnsignedNegativeValue) {
     397             :     return sr;
     398             :   }
     399             : 
     400           0 :   if (NS_FAILED(aValue->GetAsUint32(&mStorage))) {
     401             :     return ScalarResult::InvalidValue;
     402             :   }
     403           0 :   return sr;
     404             : }
     405             : 
     406             : void
     407           0 : ScalarUnsigned::SetValue(uint32_t aValue)
     408             : {
     409           0 :   mStorage = aValue;
     410           0 : }
     411             : 
     412             : ScalarResult
     413           0 : ScalarUnsigned::AddValue(nsIVariant* aValue)
     414             : {
     415           0 :   ScalarResult sr = CheckInput(aValue);
     416           0 :   if (sr == ScalarResult::UnsignedNegativeValue) {
     417             :     return sr;
     418             :   }
     419             : 
     420           0 :   uint32_t newAddend = 0;
     421           0 :   nsresult rv = aValue->GetAsUint32(&newAddend);
     422           0 :   if (NS_FAILED(rv)) {
     423             :     return ScalarResult::InvalidValue;
     424             :   }
     425           0 :   mStorage += newAddend;
     426           0 :   return sr;
     427             : }
     428             : 
     429             : void
     430           0 : ScalarUnsigned::AddValue(uint32_t aValue)
     431             : {
     432           0 :   mStorage += aValue;
     433           0 : }
     434             : 
     435             : ScalarResult
     436           0 : ScalarUnsigned::SetMaximum(nsIVariant* aValue)
     437             : {
     438           0 :   ScalarResult sr = CheckInput(aValue);
     439           0 :   if (sr == ScalarResult::UnsignedNegativeValue) {
     440             :     return sr;
     441             :   }
     442             : 
     443           0 :   uint32_t newValue = 0;
     444           0 :   nsresult rv = aValue->GetAsUint32(&newValue);
     445           0 :   if (NS_FAILED(rv)) {
     446             :     return ScalarResult::InvalidValue;
     447             :   }
     448           0 :   if (newValue > mStorage) {
     449           0 :     mStorage = newValue;
     450             :   }
     451             :   return sr;
     452             : }
     453             : 
     454             : void
     455           0 : ScalarUnsigned::SetMaximum(uint32_t aValue)
     456             : {
     457           0 :   if (aValue > mStorage) {
     458           0 :     mStorage = aValue;
     459             :   }
     460           0 : }
     461             : 
     462             : nsresult
     463           0 : ScalarUnsigned::GetValue(nsCOMPtr<nsIVariant>& aResult) const
     464             : {
     465           0 :   nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
     466           0 :   nsresult rv = outVar->SetAsUint32(mStorage);
     467           0 :   if (NS_FAILED(rv)) {
     468             :     return rv;
     469             :   }
     470           0 :   aResult = outVar.forget();
     471           0 :   return NS_OK;
     472             : }
     473             : 
     474             : size_t
     475           0 : ScalarUnsigned::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
     476             : {
     477           0 :   return aMallocSizeOf(this);
     478             : }
     479             : 
     480             : ScalarResult
     481           0 : ScalarUnsigned::CheckInput(nsIVariant* aValue)
     482             : {
     483             :   // If this is a floating point value/double, we will probably get truncated.
     484             :   uint16_t type;
     485           0 :   aValue->GetDataType(&type);
     486           0 :   if (type == nsIDataType::VTYPE_FLOAT ||
     487             :       type == nsIDataType::VTYPE_DOUBLE) {
     488             :     return ScalarResult::UnsignedTruncatedValue;
     489             :   }
     490             : 
     491             :   int32_t signedTest;
     492             :   // If we're able to cast the number to an int, check its sign.
     493             :   // Warn the user if he's trying to set the unsigned scalar to a negative
     494             :   // number.
     495           0 :   if (NS_SUCCEEDED(aValue->GetAsInt32(&signedTest)) &&
     496           0 :       signedTest < 0) {
     497             :     return ScalarResult::UnsignedNegativeValue;
     498             :   }
     499             :   return ScalarResult::Ok;
     500             : }
     501             : 
     502             : /**
     503             :  * The implementation for the string scalar type.
     504             :  */
     505             : class ScalarString : public ScalarBase
     506             : {
     507             : public:
     508             :   using ScalarBase::SetValue;
     509             : 
     510           0 :   ScalarString() : mStorage(EmptyString()) {};
     511           0 :   ~ScalarString() override = default;
     512             : 
     513             :   ScalarResult SetValue(nsIVariant* aValue) final;
     514             :   ScalarResult SetValue(const nsAString& aValue) final;
     515             :   nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final;
     516             :   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
     517             : 
     518             : private:
     519             :   nsString mStorage;
     520             : 
     521             :   // Prevent copying.
     522             :   ScalarString(const ScalarString& aOther) = delete;
     523             :   void operator=(const ScalarString& aOther) = delete;
     524             : };
     525             : 
     526             : ScalarResult
     527           0 : ScalarString::SetValue(nsIVariant* aValue)
     528             : {
     529             :   // Check that we got the correct data type.
     530             :   uint16_t type;
     531           0 :   aValue->GetDataType(&type);
     532           0 :   if (type != nsIDataType::VTYPE_CHAR &&
     533           0 :       type != nsIDataType::VTYPE_WCHAR &&
     534           0 :       type != nsIDataType::VTYPE_DOMSTRING &&
     535           0 :       type != nsIDataType::VTYPE_CHAR_STR &&
     536           0 :       type != nsIDataType::VTYPE_WCHAR_STR &&
     537           0 :       type != nsIDataType::VTYPE_STRING_SIZE_IS &&
     538           0 :       type != nsIDataType::VTYPE_WSTRING_SIZE_IS &&
     539           0 :       type != nsIDataType::VTYPE_UTF8STRING &&
     540           0 :       type != nsIDataType::VTYPE_CSTRING &&
     541             :       type != nsIDataType::VTYPE_ASTRING) {
     542             :     return ScalarResult::InvalidType;
     543             :   }
     544             : 
     545           0 :   nsAutoString convertedString;
     546           0 :   nsresult rv = aValue->GetAsAString(convertedString);
     547           0 :   if (NS_FAILED(rv)) {
     548             :     return ScalarResult::InvalidValue;
     549             :   }
     550           0 :   return SetValue(convertedString);
     551             : };
     552             : 
     553             : ScalarResult
     554           0 : ScalarString::SetValue(const nsAString& aValue)
     555             : {
     556           0 :   mStorage = Substring(aValue, 0, kMaximumStringValueLength);
     557           0 :   if (aValue.Length() > kMaximumStringValueLength) {
     558             :     return ScalarResult::StringTooLong;
     559             :   }
     560           0 :   return ScalarResult::Ok;
     561             : }
     562             : 
     563             : nsresult
     564           0 : ScalarString::GetValue(nsCOMPtr<nsIVariant>& aResult) const
     565             : {
     566           0 :   nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
     567           0 :   nsresult rv = outVar->SetAsAString(mStorage);
     568           0 :   if (NS_FAILED(rv)) {
     569             :     return rv;
     570             :   }
     571           0 :   aResult = outVar.forget();
     572           0 :   return NS_OK;
     573             : }
     574             : 
     575             : size_t
     576           0 : ScalarString::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
     577             : {
     578           0 :   size_t n = aMallocSizeOf(this);
     579           0 :   n+= mStorage.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
     580           0 :   return n;
     581             : }
     582             : 
     583             : /**
     584             :  * The implementation for the boolean scalar type.
     585             :  */
     586             : class ScalarBoolean : public ScalarBase
     587             : {
     588             : public:
     589             :   using ScalarBase::SetValue;
     590             : 
     591           0 :   ScalarBoolean() : mStorage(false) {};
     592           0 :   ~ScalarBoolean() override = default;
     593             : 
     594             :   ScalarResult SetValue(nsIVariant* aValue) final;
     595             :   void SetValue(bool aValue) final;
     596             :   nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final;
     597             :   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
     598             : 
     599             : private:
     600             :   bool mStorage;
     601             : 
     602             :   // Prevent copying.
     603             :   ScalarBoolean(const ScalarBoolean& aOther) = delete;
     604             :   void operator=(const ScalarBoolean& aOther) = delete;
     605             : };
     606             : 
     607             : ScalarResult
     608           0 : ScalarBoolean::SetValue(nsIVariant* aValue)
     609             : {
     610             :   // Check that we got the correct data type.
     611             :   uint16_t type;
     612           0 :   aValue->GetDataType(&type);
     613           0 :   if (type != nsIDataType::VTYPE_BOOL &&
     614           0 :       type != nsIDataType::VTYPE_INT8 &&
     615           0 :       type != nsIDataType::VTYPE_INT16 &&
     616           0 :       type != nsIDataType::VTYPE_INT32 &&
     617           0 :       type != nsIDataType::VTYPE_INT64 &&
     618           0 :       type != nsIDataType::VTYPE_UINT8 &&
     619           0 :       type != nsIDataType::VTYPE_UINT16 &&
     620           0 :       type != nsIDataType::VTYPE_UINT32 &&
     621             :       type != nsIDataType::VTYPE_UINT64) {
     622             :     return ScalarResult::InvalidType;
     623             :   }
     624             : 
     625           0 :   if (NS_FAILED(aValue->GetAsBool(&mStorage))) {
     626             :     return ScalarResult::InvalidValue;
     627             :   }
     628           0 :   return ScalarResult::Ok;
     629             : };
     630             : 
     631             : void
     632           0 : ScalarBoolean::SetValue(bool aValue)
     633             : {
     634           0 :   mStorage = aValue;
     635           0 : }
     636             : 
     637             : nsresult
     638           0 : ScalarBoolean::GetValue(nsCOMPtr<nsIVariant>& aResult) const
     639             : {
     640           0 :   nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
     641           0 :   nsresult rv = outVar->SetAsBool(mStorage);
     642           0 :   if (NS_FAILED(rv)) {
     643             :     return rv;
     644             :   }
     645           0 :   aResult = outVar.forget();
     646           0 :   return NS_OK;
     647             : }
     648             : 
     649             : size_t
     650           0 : ScalarBoolean::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
     651             : {
     652           0 :   return aMallocSizeOf(this);
     653             : }
     654             : 
     655             : /**
     656             :  * Allocate a scalar class given the scalar info.
     657             :  *
     658             :  * @param aInfo The informations for the scalar coming from the definition file.
     659             :  * @return nullptr if the scalar type is unknown, otherwise a valid pointer to the
     660             :  *         scalar type.
     661             :  */
     662             : ScalarBase*
     663           0 : internal_ScalarAllocate(uint32_t aScalarKind)
     664             : {
     665           0 :   ScalarBase* scalar = nullptr;
     666           0 :   switch (aScalarKind) {
     667             :   case nsITelemetry::SCALAR_TYPE_COUNT:
     668           0 :     scalar = new ScalarUnsigned();
     669           0 :     break;
     670             :   case nsITelemetry::SCALAR_TYPE_STRING:
     671           0 :     scalar = new ScalarString();
     672           0 :     break;
     673             :   case nsITelemetry::SCALAR_TYPE_BOOLEAN:
     674           0 :     scalar = new ScalarBoolean();
     675           0 :     break;
     676             :   default:
     677           0 :     MOZ_ASSERT(false, "Invalid scalar type");
     678             :   }
     679           0 :   return scalar;
     680             : }
     681             : 
     682             : /**
     683             :  * The implementation for the keyed scalar type.
     684             :  */
     685             : class KeyedScalar
     686             : {
     687             : public:
     688             :   typedef mozilla::Pair<nsCString, nsCOMPtr<nsIVariant>> KeyValuePair;
     689             : 
     690             :   explicit KeyedScalar(uint32_t aScalarKind)
     691           0 :     : mScalarKind(aScalarKind)
     692           0 :     , mMaximumNumberOfKeys(kMaximumNumberOfKeys)
     693             :   { };
     694           0 :   ~KeyedScalar() = default;
     695             : 
     696             :   // Set, Add and SetMaximum functions as described in the Telemetry IDL.
     697             :   // These methods implicitly instantiate a Scalar[*] for each key.
     698             :   ScalarResult SetValue(const nsAString& aKey, nsIVariant* aValue);
     699             :   ScalarResult AddValue(const nsAString& aKey, nsIVariant* aValue);
     700             :   ScalarResult SetMaximum(const nsAString& aKey, nsIVariant* aValue);
     701             : 
     702             :   // Convenience methods used by the C++ API.
     703             :   void SetValue(const nsAString& aKey, uint32_t aValue);
     704             :   void SetValue(const nsAString& aKey, bool aValue);
     705             :   void AddValue(const nsAString& aKey, uint32_t aValue);
     706             :   void SetMaximum(const nsAString& aKey, uint32_t aValue);
     707             : 
     708             :   // GetValue is used to get the key-value pairs stored in the keyed scalar
     709             :   // when persisting it to JS.
     710             :   nsresult GetValue(nsTArray<KeyValuePair>& aValues) const;
     711             : 
     712             :   // To measure the memory stats.
     713             :   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
     714             : 
     715             :   // To permit more keys than normal.
     716             :   void SetMaximumNumberOfKeys(uint32_t aMaximumNumberOfKeys)
     717             :   {
     718           0 :     mMaximumNumberOfKeys = aMaximumNumberOfKeys;
     719             :   };
     720             : 
     721             : private:
     722             :   typedef nsClassHashtable<nsCStringHashKey, ScalarBase> ScalarKeysMapType;
     723             : 
     724             :   ScalarKeysMapType mScalarKeys;
     725             :   const uint32_t mScalarKind;
     726             :   uint32_t mMaximumNumberOfKeys;
     727             : 
     728             :   ScalarResult GetScalarForKey(const nsAString& aKey, ScalarBase** aRet);
     729             : };
     730             : 
     731             : ScalarResult
     732           0 : KeyedScalar::SetValue(const nsAString& aKey, nsIVariant* aValue)
     733             : {
     734           0 :   ScalarBase* scalar = nullptr;
     735           0 :   ScalarResult sr = GetScalarForKey(aKey, &scalar);
     736           0 :   if (sr != ScalarResult::Ok) {
     737             :     return sr;
     738             :   }
     739             : 
     740           0 :   return scalar->SetValue(aValue);
     741             : }
     742             : 
     743             : ScalarResult
     744           0 : KeyedScalar::AddValue(const nsAString& aKey, nsIVariant* aValue)
     745             : {
     746           0 :   ScalarBase* scalar = nullptr;
     747           0 :   ScalarResult sr = GetScalarForKey(aKey, &scalar);
     748           0 :   if (sr != ScalarResult::Ok) {
     749             :     return sr;
     750             :   }
     751             : 
     752           0 :   return scalar->AddValue(aValue);
     753             : }
     754             : 
     755             : ScalarResult
     756           0 : KeyedScalar::SetMaximum(const nsAString& aKey, nsIVariant* aValue)
     757             : {
     758           0 :   ScalarBase* scalar = nullptr;
     759           0 :   ScalarResult sr = GetScalarForKey(aKey, &scalar);
     760           0 :   if (sr != ScalarResult::Ok) {
     761             :     return sr;
     762             :   }
     763             : 
     764           0 :   return scalar->SetMaximum(aValue);
     765             : }
     766             : 
     767             : void
     768           0 : KeyedScalar::SetValue(const nsAString& aKey, uint32_t aValue)
     769             : {
     770           0 :   ScalarBase* scalar = nullptr;
     771           0 :   ScalarResult sr = GetScalarForKey(aKey, &scalar);
     772           0 :   if (sr != ScalarResult::Ok) {
     773           0 :     MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
     774             :     return;
     775             :   }
     776             : 
     777           0 :   return scalar->SetValue(aValue);
     778             : }
     779             : 
     780             : void
     781           0 : KeyedScalar::SetValue(const nsAString& aKey, bool aValue)
     782             : {
     783           0 :   ScalarBase* scalar = nullptr;
     784           0 :   ScalarResult sr = GetScalarForKey(aKey, &scalar);
     785           0 :   if (sr != ScalarResult::Ok) {
     786           0 :     MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
     787             :     return;
     788             :   }
     789             : 
     790           0 :   return scalar->SetValue(aValue);
     791             : }
     792             : 
     793             : void
     794           0 : KeyedScalar::AddValue(const nsAString& aKey, uint32_t aValue)
     795             : {
     796           0 :   ScalarBase* scalar = nullptr;
     797           0 :   ScalarResult sr = GetScalarForKey(aKey, &scalar);
     798           0 :   if (sr != ScalarResult::Ok) {
     799           0 :     MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
     800             :     return;
     801             :   }
     802             : 
     803           0 :   return scalar->AddValue(aValue);
     804             : }
     805             : 
     806             : void
     807           0 : KeyedScalar::SetMaximum(const nsAString& aKey, uint32_t aValue)
     808             : {
     809           0 :   ScalarBase* scalar = nullptr;
     810           0 :   ScalarResult sr = GetScalarForKey(aKey, &scalar);
     811           0 :   if (sr != ScalarResult::Ok) {
     812           0 :     MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
     813             :     return;
     814             :   }
     815             : 
     816           0 :   return scalar->SetMaximum(aValue);
     817             : }
     818             : 
     819             : /**
     820             :  * Get a key-value array with the values for the Keyed Scalar.
     821             :  * @param aValue The array that will hold the key-value pairs.
     822             :  * @return {nsresult} NS_OK or an error value as reported by the
     823             :  *         the specific scalar objects implementations (e.g.
     824             :  *         ScalarUnsigned).
     825             :  */
     826             : nsresult
     827           0 : KeyedScalar::GetValue(nsTArray<KeyValuePair>& aValues) const
     828             : {
     829           0 :   for (auto iter = mScalarKeys.ConstIter(); !iter.Done(); iter.Next()) {
     830           0 :     ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
     831             : 
     832             :     // Get the scalar value.
     833           0 :     nsCOMPtr<nsIVariant> scalarValue;
     834           0 :     nsresult rv = scalar->GetValue(scalarValue);
     835           0 :     if (NS_FAILED(rv)) {
     836           0 :       return rv;
     837             :     }
     838             : 
     839             :     // Append it to value list.
     840           0 :     aValues.AppendElement(mozilla::MakePair(nsCString(iter.Key()), scalarValue));
     841             :   }
     842             : 
     843           0 :   return NS_OK;
     844             : }
     845             : 
     846             : /**
     847             :  * Get the scalar for the referenced key.
     848             :  * If there's no such key, instantiate a new Scalar object with the
     849             :  * same type of the Keyed scalar and create the key.
     850             :  */
     851             : ScalarResult
     852           0 : KeyedScalar::GetScalarForKey(const nsAString& aKey, ScalarBase** aRet)
     853             : {
     854           0 :   if (aKey.IsEmpty()) {
     855             :     return ScalarResult::KeyIsEmpty;
     856             :   }
     857             : 
     858           0 :   if (aKey.Length() > kMaximumKeyStringLength) {
     859             :     return ScalarResult::KeyTooLong;
     860             :   }
     861             : 
     862           0 :   NS_ConvertUTF16toUTF8 utf8Key(aKey);
     863             : 
     864           0 :   ScalarBase* scalar = nullptr;
     865           0 :   if (mScalarKeys.Get(utf8Key, &scalar)) {
     866           0 :     *aRet = scalar;
     867           0 :     return ScalarResult::Ok;
     868             :   }
     869             : 
     870           0 :   if (mScalarKeys.Count() >= mMaximumNumberOfKeys) {
     871             :     return ScalarResult::TooManyKeys;
     872             :   }
     873             : 
     874           0 :   scalar = internal_ScalarAllocate(mScalarKind);
     875           0 :   if (!scalar) {
     876             :     return ScalarResult::InvalidType;
     877             :   }
     878             : 
     879           0 :   mScalarKeys.Put(utf8Key, scalar);
     880             : 
     881           0 :   *aRet = scalar;
     882           0 :   return ScalarResult::Ok;
     883             : }
     884             : 
     885             : size_t
     886           0 : KeyedScalar::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
     887             : {
     888           0 :   size_t n = aMallocSizeOf(this);
     889           0 :   for (auto iter = mScalarKeys.Iter(); !iter.Done(); iter.Next()) {
     890           0 :     ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
     891           0 :     n += scalar->SizeOfIncludingThis(aMallocSizeOf);
     892             :   }
     893           0 :   return n;
     894             : }
     895             : 
     896             : typedef nsUint32HashKey ScalarIDHashKey;
     897             : typedef nsUint32HashKey ProcessIDHashKey;
     898             : typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType;
     899             : typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar> KeyedScalarStorageMapType;
     900             : typedef nsClassHashtable<ProcessIDHashKey, ScalarStorageMapType> ProcessesScalarsMapType;
     901             : typedef nsClassHashtable<ProcessIDHashKey, KeyedScalarStorageMapType> ProcessesKeyedScalarsMapType;
     902             : 
     903             : typedef mozilla::Tuple<const char*, nsCOMPtr<nsIVariant>, uint32_t> ScalarDataTuple;
     904             : typedef nsTArray<ScalarDataTuple> ScalarTupleArray;
     905             : typedef nsDataHashtable<ProcessIDHashKey, ScalarTupleArray> ScalarSnapshotTable;
     906             : 
     907             : typedef mozilla::Tuple<const char*, nsTArray<KeyedScalar::KeyValuePair>, uint32_t> KeyedScalarDataTuple;
     908             : typedef nsTArray<KeyedScalarDataTuple> KeyedScalarTupleArray;
     909             : typedef nsDataHashtable<ProcessIDHashKey, KeyedScalarTupleArray> KeyedScalarSnapshotTable;
     910             : 
     911             : } // namespace
     912             : 
     913             : ////////////////////////////////////////////////////////////////////////
     914             : ////////////////////////////////////////////////////////////////////////
     915             : //
     916             : // PRIVATE STATE, SHARED BY ALL THREADS
     917             : 
     918             : namespace {
     919             : 
     920             : // Set to true once this global state has been initialized.
     921             : bool gInitDone = false;
     922             : 
     923             : bool gCanRecordBase;
     924             : bool gCanRecordExtended;
     925             : 
     926             : // The Name -> ID cache map.
     927           0 : ScalarMapType gScalarNameIDMap(kScalarCount);
     928             : 
     929             : // The (Process Id -> (Scalar ID -> Scalar Object)) map. This is a nsClassHashtable,
     930             : // it owns the scalar instances and takes care of deallocating them when they are
     931             : // removed from the map.
     932           0 : ProcessesScalarsMapType gScalarStorageMap;
     933             : // As above, for the keyed scalars.
     934           0 : ProcessesKeyedScalarsMapType gKeyedScalarStorageMap;
     935             : // Provide separate storage for "dynamic builtin" plain and keyed scalars,
     936             : // needed to support "build faster" in local developer builds.
     937           0 : ProcessesScalarsMapType gDynamicBuiltinScalarStorageMap;
     938           0 : ProcessesKeyedScalarsMapType gDynamicBuiltinKeyedScalarStorageMap;
     939             : 
     940             : // Whether or not the deserialization of persisted scalars is still in progress.
     941             : // This is never the case on Desktop or Fennec.
     942             : // Only GeckoView restores persisted scalars.
     943             : bool gIsDeserializing = false;
     944             : // This batches scalar accumulations that should be applied once loading finished.
     945           0 : StaticAutoPtr<nsTArray<ScalarAction>> gScalarsActions;
     946           0 : StaticAutoPtr<nsTArray<KeyedScalarAction>> gKeyedScalarsActions;
     947             : 
     948             : bool
     949             : internal_IsScalarDeserializing(const StaticMutexAutoLock& lock)
     950             : {
     951           0 :   return gIsDeserializing;
     952             : }
     953             : 
     954             : } // namespace
     955             : 
     956             : ////////////////////////////////////////////////////////////////////////
     957             : ////////////////////////////////////////////////////////////////////////
     958             : //
     959             : // PRIVATE: Function that may call JS code.
     960             : 
     961             : // NOTE: the functions in this section all run without protection from
     962             : // |gTelemetryScalarsMutex|. If they held the mutex, there would be the
     963             : // possibility of deadlock because the JS_ calls that they make may call
     964             : // back into the TelemetryScalar interface, hence trying to re-acquire the mutex.
     965             : //
     966             : // This means that these functions potentially race against threads, but
     967             : // that seems preferable to risking deadlock.
     968             : 
     969             : namespace {
     970             : 
     971             : /**
     972             :  * Converts the error code to a human readable error message and prints it to the
     973             :  * browser console.
     974             :  *
     975             :  * @param aScalarName The name of the scalar that raised the error.
     976             :  * @param aSr The error code.
     977             :  */
     978             : void
     979           0 : internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr)
     980             : {
     981           0 :   nsAutoString errorMessage;
     982           0 :   AppendUTF8toUTF16(aScalarName, errorMessage);
     983             : 
     984           0 :   switch (aSr) {
     985             :     case ScalarResult::NotInitialized:
     986           0 :       errorMessage.AppendLiteral(u" - Telemetry was not yet initialized.");
     987           0 :       break;
     988             :     case ScalarResult::CannotUnpackVariant:
     989           0 :       errorMessage.AppendLiteral(u" - Cannot convert the provided JS value to nsIVariant.");
     990           0 :       break;
     991             :     case ScalarResult::CannotRecordInProcess:
     992           0 :       errorMessage.AppendLiteral(u" - Cannot record the scalar in the current process.");
     993           0 :       break;
     994             :     case ScalarResult::KeyedTypeMismatch:
     995           0 :       errorMessage.AppendLiteral(u" - Attempting to manage a keyed scalar as a scalar (or vice-versa).");
     996           0 :       break;
     997             :     case ScalarResult::UnknownScalar:
     998           0 :       errorMessage.AppendLiteral(u" - Unknown scalar.");
     999           0 :       break;
    1000             :     case ScalarResult::OperationNotSupported:
    1001           0 :       errorMessage.AppendLiteral(u" - The requested operation is not supported on this scalar.");
    1002           0 :       break;
    1003             :     case ScalarResult::InvalidType:
    1004           0 :       errorMessage.AppendLiteral(u" - Attempted to set the scalar to an invalid data type.");
    1005           0 :       break;
    1006             :     case ScalarResult::InvalidValue:
    1007           0 :       errorMessage.AppendLiteral(u" - Attempted to set the scalar to an incompatible value.");
    1008           0 :       break;
    1009             :     case ScalarResult::StringTooLong:
    1010           0 :       AppendUTF8toUTF16(nsPrintfCString(" - Truncating scalar value to %d characters.", kMaximumStringValueLength), errorMessage);
    1011           0 :       break;
    1012             :     case ScalarResult::KeyIsEmpty:
    1013           0 :       errorMessage.AppendLiteral(u" - The key must not be empty.");
    1014           0 :       break;
    1015             :     case ScalarResult::KeyTooLong:
    1016           0 :       AppendUTF8toUTF16(nsPrintfCString(" - The key length must be limited to %d characters.", kMaximumKeyStringLength), errorMessage);
    1017           0 :       break;
    1018             :     case ScalarResult::TooManyKeys:
    1019           0 :       AppendUTF8toUTF16(nsPrintfCString(" - Keyed scalars cannot have more than %d keys.", kMaximumNumberOfKeys), errorMessage);
    1020           0 :       break;
    1021             :     case ScalarResult::UnsignedNegativeValue:
    1022           0 :       errorMessage.AppendLiteral(u" - Trying to set an unsigned scalar to a negative number.");
    1023           0 :       break;
    1024             :     case ScalarResult::UnsignedTruncatedValue:
    1025           0 :       errorMessage.AppendLiteral(u" - Truncating float/double number.");
    1026           0 :       break;
    1027             :     default:
    1028             :       // Nothing.
    1029           0 :       return;
    1030             :   }
    1031             : 
    1032           0 :   LogToBrowserConsole(nsIScriptError::warningFlag, errorMessage);
    1033             : }
    1034             : 
    1035             : } // namespace
    1036             : 
    1037             : ////////////////////////////////////////////////////////////////////////
    1038             : ////////////////////////////////////////////////////////////////////////
    1039             : //
    1040             : // PRIVATE: helpers for the external interface
    1041             : 
    1042             : namespace {
    1043             : 
    1044             : bool
    1045             : internal_CanRecordBase(const StaticMutexAutoLock& lock)
    1046             : {
    1047           0 :   return gCanRecordBase;
    1048             : }
    1049             : 
    1050             : bool
    1051             : internal_CanRecordExtended(const StaticMutexAutoLock& lock)
    1052             : {
    1053           0 :   return gCanRecordExtended;
    1054             : }
    1055             : 
    1056             : /**
    1057             :  * Check if the given scalar is a keyed scalar.
    1058             :  *
    1059             :  * @param lock Instance of a lock locking gTelemetryHistogramMutex
    1060             :  * @param aId The scalar identifier.
    1061             :  * @return true if aId refers to a keyed scalar, false otherwise.
    1062             :  */
    1063             : bool
    1064           0 : internal_IsKeyedScalar(const StaticMutexAutoLock& lock, const ScalarKey& aId)
    1065             : {
    1066           0 :   return internal_GetScalarInfo(lock, aId).keyed;
    1067             : }
    1068             : 
    1069             : /**
    1070             :  * Check if we're allowed to record the given scalar in the current
    1071             :  * process.
    1072             :  *
    1073             :  * @param lock Instance of a lock locking gTelemetryHistogramMutex
    1074             :  * @param aId The scalar identifier.
    1075             :  * @return true if the scalar is allowed to be recorded in the current process, false
    1076             :  *         otherwise.
    1077             :  */
    1078             : bool
    1079           0 : internal_CanRecordProcess(const StaticMutexAutoLock& lock,
    1080             :                           const ScalarKey& aId)
    1081             : {
    1082           0 :   const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
    1083           0 :   return CanRecordInProcess(info.record_in_processes, XRE_GetProcessType());
    1084             : }
    1085             : 
    1086             : bool
    1087           0 : internal_CanRecordProduct(const StaticMutexAutoLock& lock,
    1088             :                           const ScalarKey& aId)
    1089             : {
    1090           0 :   const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
    1091           0 :   return CanRecordProduct(info.products);
    1092             : }
    1093             : 
    1094             : bool
    1095           0 : internal_CanRecordForScalarID(const StaticMutexAutoLock& lock,
    1096             :                               const ScalarKey& aId)
    1097             : {
    1098             :   // Get the scalar info from the id.
    1099           0 :   const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
    1100             : 
    1101             :   // Can we record at all?
    1102           0 :   bool canRecordBase = internal_CanRecordBase(lock);
    1103           0 :   if (!canRecordBase) {
    1104             :     return false;
    1105             :   }
    1106             : 
    1107           0 :   bool canRecordDataset = CanRecordDataset(info.dataset,
    1108             :                                            canRecordBase,
    1109           0 :                                            internal_CanRecordExtended(lock));
    1110           0 :   if (!canRecordDataset) {
    1111             :     return false;
    1112             :   }
    1113             : 
    1114             :   return true;
    1115             : }
    1116             : 
    1117             : /**
    1118             :  * Check if we are allowed to record the provided scalar.
    1119             :  *
    1120             :  * @param lock Instance of a lock locking gTelemetryHistogramMutex
    1121             :  * @param aId The scalar identifier.
    1122             :  * @param aKeyed Are we attempting to write a keyed scalar?
    1123             :  * @return ScalarResult::Ok if we can record, an error code otherwise.
    1124             :  */
    1125             : ScalarResult
    1126           0 : internal_CanRecordScalar(const StaticMutexAutoLock& lock, const ScalarKey& aId,
    1127             :                          bool aKeyed)
    1128             : {
    1129             :   // Make sure that we have a keyed scalar if we are trying to change one.
    1130           0 :   if (internal_IsKeyedScalar(lock, aId) != aKeyed) {
    1131             :     return ScalarResult::KeyedTypeMismatch;
    1132             :   }
    1133             : 
    1134             :   // Are we allowed to record this scalar based on the current Telemetry
    1135             :   // settings?
    1136           0 :   if (!internal_CanRecordForScalarID(lock, aId)) {
    1137             :     return ScalarResult::CannotRecordDataset;
    1138             :   }
    1139             : 
    1140             :   // Can we record in this process?
    1141           0 :   if (!internal_CanRecordProcess(lock, aId)) {
    1142             :     return ScalarResult::CannotRecordInProcess;
    1143             :   }
    1144             : 
    1145             :   // Can we record on this product?
    1146           0 :   if (!internal_CanRecordProduct(lock, aId)) {
    1147             :     return ScalarResult::CannotRecordDataset;
    1148             :   }
    1149             : 
    1150           0 :   return ScalarResult::Ok;
    1151             : }
    1152             : 
    1153             : /**
    1154             :  * Get the scalar enum id from the scalar name.
    1155             :  *
    1156             :  * @param lock Instance of a lock locking gTelemetryHistogramMutex
    1157             :  * @param aName The scalar name.
    1158             :  * @param aId The output variable to contain the enum.
    1159             :  * @return
    1160             :  *   NS_ERROR_FAILURE if this was called before init is completed.
    1161             :  *   NS_ERROR_INVALID_ARG if the name can't be found in the scalar definitions.
    1162             :  *   NS_OK if the scalar was found and aId contains a valid enum id.
    1163             :  */
    1164             : nsresult
    1165           0 : internal_GetEnumByScalarName(const StaticMutexAutoLock& lock,
    1166             :                              const nsACString& aName,
    1167             :                              ScalarKey* aId)
    1168             : {
    1169           0 :   if (!gInitDone) {
    1170             :     return NS_ERROR_FAILURE;
    1171             :   }
    1172             : 
    1173           0 :   CharPtrEntryType *entry = gScalarNameIDMap.GetEntry(PromiseFlatCString(aName).get());
    1174           0 :   if (!entry) {
    1175             :     return NS_ERROR_INVALID_ARG;
    1176             :   }
    1177           0 :   *aId = entry->mData;
    1178             :   return NS_OK;
    1179             : }
    1180             : 
    1181             : /**
    1182             :  * Get a scalar object by its enum id. This implicitly allocates the scalar
    1183             :  * object in the storage if it wasn't previously allocated.
    1184             :  *
    1185             :  * @param lock Instance of a lock locking gTelemetryHistogramMutex
    1186             :  * @param aId The scalar identifier.
    1187             :  * @param aProcessStorage This drives the selection of the map to use to store
    1188             :  *        the scalar data coming from child processes. This is only meaningful when
    1189             :  *        this function is called in parent process. If that's the case, if
    1190             :  *        this is not |GeckoProcessType_Default|, the process id is used to
    1191             :  *        allocate and store the scalars.
    1192             :  * @param aRes The output variable that stores scalar object.
    1193             :  * @return
    1194             :  *   NS_ERROR_INVALID_ARG if the scalar id is unknown.
    1195             :  *   NS_ERROR_NOT_AVAILABLE if the scalar is expired.
    1196             :  *   NS_OK if the scalar was found. If that's the case, aResult contains a
    1197             :  *   valid pointer to a scalar type.
    1198             :  */
    1199             : nsresult
    1200           0 : internal_GetScalarByEnum(const StaticMutexAutoLock& lock,
    1201             :                          const ScalarKey& aId,
    1202             :                          ProcessID aProcessStorage,
    1203             :                          ScalarBase** aRet)
    1204             : {
    1205           0 :   if (!internal_IsValidId(lock, aId)) {
    1206           0 :     MOZ_ASSERT(false, "Requested a scalar with an invalid id.");
    1207             :     return NS_ERROR_INVALID_ARG;
    1208             :   }
    1209             : 
    1210           0 :   const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
    1211             : 
    1212             :   // Dynamic scalars fixup: they are always stored in the "dynamic" process,
    1213             :   // unless they are part of the "builtin" Firefox probes. Please note that
    1214             :   // "dynamic builtin" probes are meant to support "artifact" and "build faster"
    1215             :   // builds.
    1216           0 :   if (aId.dynamic && !info.builtin) {
    1217           0 :     aProcessStorage = ProcessID::Dynamic;
    1218             :   }
    1219             : 
    1220           0 :   ScalarBase* scalar = nullptr;
    1221           0 :   ScalarStorageMapType* scalarStorage = nullptr;
    1222             :   // Initialize the scalar storage to the parent storage. This will get
    1223             :   // set to the child storage if needed.
    1224           0 :   uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
    1225             : 
    1226             :   // Put dynamic-builtin scalars (used to support "build faster") in a
    1227             :   // separate storage.
    1228             :   ProcessesScalarsMapType& processStorage =
    1229           0 :     (aId.dynamic && info.builtin) ? gDynamicBuiltinScalarStorageMap : gScalarStorageMap;
    1230             : 
    1231             :   // Get the process-specific storage or create one if it's not
    1232             :   // available.
    1233           0 :   if (!processStorage.Get(storageId, &scalarStorage)) {
    1234           0 :     scalarStorage = new ScalarStorageMapType();
    1235           0 :     processStorage.Put(storageId, scalarStorage);
    1236             :   }
    1237             : 
    1238             :   // Check if the scalar is already allocated in the parent or in the child storage.
    1239           0 :   if (scalarStorage->Get(aId.id, &scalar)) {
    1240             :     // Dynamic scalars can expire at any time during the session (e.g. an
    1241             :     // add-on was updated). Check if it expired.
    1242           0 :     if (aId.dynamic) {
    1243           0 :       const DynamicScalarInfo& dynInfo = static_cast<const DynamicScalarInfo&>(info);
    1244           0 :       if (dynInfo.mDynamicExpiration) {
    1245             :         // The Dynamic scalar is expired.
    1246             :         return NS_ERROR_NOT_AVAILABLE;
    1247             :       }
    1248             :     }
    1249             :     // This was not a dynamic scalar or was not expired.
    1250           0 :     *aRet = scalar;
    1251             :     return NS_OK;
    1252             :   }
    1253             : 
    1254             :   // The scalar storage wasn't already allocated. Check if the scalar is expired and
    1255             :   // then allocate the storage, if needed.
    1256           0 :   if (IsExpiredVersion(info.expiration())) {
    1257             :     return NS_ERROR_NOT_AVAILABLE;
    1258             :   }
    1259             : 
    1260           0 :   scalar = internal_ScalarAllocate(info.kind);
    1261           0 :   if (!scalar) {
    1262             :     return NS_ERROR_INVALID_ARG;
    1263             :   }
    1264             : 
    1265           0 :   scalarStorage->Put(aId.id, scalar);
    1266           0 :   *aRet = scalar;
    1267             :   return NS_OK;
    1268             : }
    1269             : 
    1270             : void internal_ApplyPendingOperations(const StaticMutexAutoLock& lock);
    1271             : 
    1272             : /**
    1273             :  * Record that the high-water mark for the pending operations list was reached once.
    1274             :  *
    1275             :  * Important:
    1276             :  * This appends one additional operation.
    1277             :  * This needs to happen while still in deserialization mode.
    1278             :  */
    1279           0 : void internal_RecordHighwatermarkReached(const StaticMutexAutoLock& lock)
    1280             : {
    1281           0 :   MOZ_ASSERT(gIsDeserializing);
    1282           0 :   MOZ_ASSERT(gScalarsActions);
    1283             : 
    1284             :   // We can't call `internal_RecordScalarAction` here, because we are already
    1285             :   // getting called from there after the high-water mark check.
    1286             :   // But we know that `gScalarsActions` is a valid array and can append directly.
    1287           0 :   ScalarID id = ScalarID::TELEMETRY_PENDING_OPERATIONS_HIGHWATERMARK_REACHED;
    1288             :   ScalarAction action{
    1289             :     static_cast<uint32_t>(id), false, ScalarActionType::eAdd,
    1290           0 :     Some(ScalarVariant(1u)), ProcessID::Parent
    1291           0 :   };
    1292           0 :   gScalarsActions->AppendElement(action);
    1293           0 : }
    1294             : 
    1295             : /**
    1296             :  * Record the given action on a scalar into the pending actions list.
    1297             :  *
    1298             :  * If the pending actions list overflows the high water mark length
    1299             :  * all operations are immediately applied, including the passed action.
    1300             :  *
    1301             :  * @param aScalarAction The action to record.
    1302             :  */
    1303             : void
    1304           0 : internal_RecordScalarAction(const StaticMutexAutoLock& lock,
    1305             :                             const ScalarAction& aScalarAction)
    1306             : {
    1307             :   // Make sure to have the storage.
    1308           0 :   if (!gScalarsActions) {
    1309           0 :     gScalarsActions = new nsTArray<ScalarAction>();
    1310             :   }
    1311             : 
    1312             :   // Store the action.
    1313           0 :   gScalarsActions->AppendElement(aScalarAction);
    1314             : 
    1315             :   // If this action overflows the pending actions array, we immediately apply pending operations
    1316             :   // and assume loading is over.
    1317             :   // If loading still happens afterwards, some scalar values might be
    1318             :   // overwritten and inconsistent, but we won't lose operations on otherwise untouched probes.
    1319           0 :   if (gScalarsActions->Length() > kScalarActionsArrayHighWaterMark) {
    1320           0 :     internal_RecordHighwatermarkReached(lock);
    1321           0 :     internal_ApplyPendingOperations(lock);
    1322             :     return;
    1323             :   }
    1324             : }
    1325             : 
    1326             : /**
    1327             :  * Record the given action on a scalar on the main process into the pending actions list.
    1328             :  *
    1329             :  * If the pending actions list overflows the high water mark length
    1330             :  * all operations are immediately applied, including the passed action.
    1331             :  *
    1332             :  * @param aId The scalar's ID this action applies to
    1333             :  * @param aDynamic Determines if the scalar is dynamic
    1334             :  * @param aAction The action to record
    1335             :  * @param aValue The additional data for the recorded action
    1336             :  */
    1337             : void
    1338           0 : internal_RecordScalarAction(const StaticMutexAutoLock& lock,
    1339             :                             uint32_t aId, bool aDynamic,
    1340             :                             ScalarActionType aAction, const ScalarVariant& aValue)
    1341             : {
    1342           0 :   internal_RecordScalarAction(lock, ScalarAction{
    1343             :                                       aId, aDynamic, aAction,
    1344             :                                       Some(aValue), ProcessID::Parent
    1345           0 :                                     });
    1346           0 : }
    1347             : 
    1348             : /**
    1349             :  * Record the given action on a keyed scalar into the pending actions list.
    1350             :  *
    1351             :  * If the pending actions list overflows the high water mark length
    1352             :  * all operations are immediately applied, including the passed action.
    1353             :  *
    1354             :  * @param aScalarAction The action to record.
    1355             :  */
    1356             : void
    1357           0 : internal_RecordKeyedScalarAction(const StaticMutexAutoLock& lock,
    1358             :                                  const KeyedScalarAction& aScalarAction)
    1359             : {
    1360             :   // Make sure to have the storage.
    1361           0 :   if (!gKeyedScalarsActions) {
    1362           0 :     gKeyedScalarsActions = new nsTArray<KeyedScalarAction>();
    1363             :   }
    1364             : 
    1365             :   // Store the action.
    1366           0 :   gKeyedScalarsActions->AppendElement(aScalarAction);
    1367             : 
    1368             :   // If this action overflows the pending actions array, we immediately apply pending operations
    1369             :   // and assume loading is over.
    1370             :   // If loading still happens afterwards, some scalar values might be
    1371             :   // overwritten and inconsistent, but we won't lose operations on otherwise untouched probes.
    1372           0 :   if (gKeyedScalarsActions->Length() > kScalarActionsArrayHighWaterMark) {
    1373           0 :     internal_RecordHighwatermarkReached(lock);
    1374           0 :     internal_ApplyPendingOperations(lock);
    1375             :     return;
    1376             :   }
    1377             : }
    1378             : 
    1379             : /**
    1380             :  * Record the given action on a keyed scalar on the main process into the pending actions list.
    1381             :  *
    1382             :  * If the pending actions list overflows the high water mark length
    1383             :  * all operations are immediately applied, including the passed action.
    1384             :  *
    1385             :  * @param aId The scalar's ID this action applies to
    1386             :  * @param aDynamic Determines if the scalar is dynamic
    1387             :  * @param aKey The scalar's key
    1388             :  * @param aAction The action to record
    1389             :  * @param aValue The additional data for the recorded action
    1390             :  */
    1391             : void
    1392           0 : internal_RecordKeyedScalarAction(const StaticMutexAutoLock& lock,
    1393             :                                  uint32_t aId, bool aDynamic,
    1394             :                                  const nsAString& aKey,
    1395             :                                  ScalarActionType aAction,
    1396             :                                  const ScalarVariant& aValue)
    1397             : {
    1398           0 :   internal_RecordKeyedScalarAction(lock, KeyedScalarAction{
    1399           0 :                                            aId, aDynamic, aAction, NS_ConvertUTF16toUTF8(aKey),
    1400             :                                            Some(aValue), ProcessID::Parent
    1401           0 :                                          });
    1402           0 : }
    1403             : 
    1404             : /**
    1405             :  * Update the scalar with the provided value. This is used by the JS API.
    1406             :  *
    1407             :  * @param lock Instance of a lock locking gTelemetryHistogramMutex
    1408             :  * @param aName The scalar name.
    1409             :  * @param aType The action type for updating the scalar.
    1410             :  * @param aValue The value to use for updating the scalar.
    1411             :  * @param aProcessOverride The process for which the scalar must be updated.
    1412             :  *        This must only be used for GeckoView persistence. It must be
    1413             :  *        set to the ProcessID::Parent for all the other cases.
    1414             :  * @param aForce Whether to force updating even if load is in progress.
    1415             :  * @return a ScalarResult error value.
    1416             :  */
    1417             : ScalarResult
    1418           0 : internal_UpdateScalar(const StaticMutexAutoLock& lock, const nsACString& aName,
    1419             :                       ScalarActionType aType, nsIVariant* aValue,
    1420             :                       ProcessID aProcessOverride = ProcessID::Parent,
    1421             :                       bool aForce = false)
    1422             : {
    1423             :   ScalarKey uniqueId;
    1424           0 :   nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
    1425           0 :   if (NS_FAILED(rv)) {
    1426           0 :     return (rv == NS_ERROR_FAILURE) ?
    1427             :            ScalarResult::NotInitialized : ScalarResult::UnknownScalar;
    1428             :   }
    1429             : 
    1430           5 :   ScalarResult sr = internal_CanRecordScalar(lock, uniqueId, false);
    1431           1 :   if (sr != ScalarResult::Ok) {
    1432           1 :     if (sr == ScalarResult::CannotRecordDataset) {
    1433             :       return ScalarResult::Ok;
    1434             :     }
    1435           0 :     return sr;
    1436             :   }
    1437             : 
    1438             :   // Accumulate in the child process if needed.
    1439           4 :   if (!XRE_IsParentProcess()) {
    1440           0 :     const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
    1441             :     // Convert the nsIVariant to a Variant.
    1442           0 :     mozilla::Maybe<ScalarVariant> variantValue;
    1443           0 :     sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
    1444           0 :     if (sr != ScalarResult::Ok) {
    1445           0 :       MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
    1446             :       return sr;
    1447             :     }
    1448           0 :     TelemetryIPCAccumulator::RecordChildScalarAction(
    1449           0 :       uniqueId.id, uniqueId.dynamic, aType, variantValue.ref());
    1450           0 :     return ScalarResult::Ok;
    1451             :   }
    1452             : 
    1453           0 :   if (!aForce && internal_IsScalarDeserializing(lock)) {
    1454           0 :     const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
    1455             :     // Convert the nsIVariant to a Variant.
    1456           0 :     mozilla::Maybe<ScalarVariant> variantValue;
    1457           0 :     sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
    1458           0 :     if (sr != ScalarResult::Ok) {
    1459           0 :       MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
    1460             :       return sr;
    1461             :     }
    1462           0 :     internal_RecordScalarAction(lock, uniqueId.id, uniqueId.dynamic, aType, variantValue.ref());
    1463           0 :     return ScalarResult::Ok;
    1464             :   }
    1465             : 
    1466             :   // Finally get the scalar.
    1467           4 :   ScalarBase* scalar = nullptr;
    1468           4 :   rv = internal_GetScalarByEnum(lock, uniqueId, aProcessOverride, &scalar);
    1469           4 :   if (NS_FAILED(rv)) {
    1470             :     // Don't throw on expired scalars.
    1471           0 :     if (rv == NS_ERROR_NOT_AVAILABLE) {
    1472             :       return ScalarResult::Ok;
    1473             :     }
    1474           0 :     return ScalarResult::UnknownScalar;
    1475             :   }
    1476             : 
    1477           4 :   if (aType == ScalarActionType::eAdd) {
    1478           1 :     return scalar->AddValue(aValue);
    1479             :   }
    1480           3 :   if (aType == ScalarActionType::eSet) {
    1481           1 :     return scalar->SetValue(aValue);
    1482             :   }
    1483             : 
    1484           2 :   return scalar->SetMaximum(aValue);
    1485             : }
    1486             : 
    1487             : } // namespace
    1488             : 
    1489             : 
    1490             : 
    1491             : ////////////////////////////////////////////////////////////////////////
    1492             : ////////////////////////////////////////////////////////////////////////
    1493             : //
    1494             : // PRIVATE: thread-unsafe helpers for the keyed scalars
    1495             : 
    1496             : namespace {
    1497             : 
    1498             : /**
    1499             :  * Get a keyed scalar object by its enum id. This implicitly allocates the keyed
    1500             :  * scalar object in the storage if it wasn't previously allocated.
    1501             :  *
    1502             :  * @param lock Instance of a lock locking gTelemetryHistogramMutex
    1503             :  * @param aId The scalar identifier.
    1504             :  * @param aProcessStorage This drives the selection of the map to use to store
    1505             :  *        the scalar data coming from child processes. This is only meaningful when
    1506             :  *        this function is called in parent process. If that's the case, if
    1507             :  *        this is not |GeckoProcessType_Default|, the process id is used to
    1508             :  *        allocate and store the scalars.
    1509             :  * @param aRet The output variable that stores scalar object.
    1510             :  * @return
    1511             :  *   NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed string
    1512             :  *                        scalar.
    1513             :  *   NS_ERROR_NOT_AVAILABLE if the scalar is expired.
    1514             :  *   NS_OK if the scalar was found. If that's the case, aResult contains a
    1515             :  *   valid pointer to a scalar type.
    1516             :  */
    1517             : nsresult
    1518           2 : internal_GetKeyedScalarByEnum(const StaticMutexAutoLock& lock,
    1519             :                               const ScalarKey& aId,
    1520             :                               ProcessID aProcessStorage,
    1521             :                               KeyedScalar** aRet)
    1522             : {
    1523           2 :   if (!internal_IsValidId(lock, aId)) {
    1524           0 :     MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id.");
    1525             :     return NS_ERROR_INVALID_ARG;
    1526             :   }
    1527             : 
    1528           0 :   const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
    1529             : 
    1530             :   // Dynamic scalars fixup: they are always stored in the "dynamic" process,
    1531             :   // unless they are part of the "builtin" Firefox probes. Please note that
    1532             :   // "dynamic builtin" probes are meant to support "artifact" and "build faster"
    1533             :   // builds.
    1534           2 :   if (aId.dynamic && !info.builtin) {
    1535           0 :     aProcessStorage = ProcessID::Dynamic;
    1536             :   }
    1537             : 
    1538           2 :   KeyedScalar* scalar = nullptr;
    1539           2 :   KeyedScalarStorageMapType* scalarStorage = nullptr;
    1540             :   // Initialize the scalar storage to the parent storage. This will get
    1541             :   // set to the child storage if needed.
    1542           1 :   uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
    1543             : 
    1544             :   // Put dynamic-builtin scalars (used to support "build faster") in a
    1545             :   // separate storage.
    1546             :   ProcessesKeyedScalarsMapType& processStorage =
    1547           0 :     (aId.dynamic && info.builtin) ? gDynamicBuiltinKeyedScalarStorageMap : gKeyedScalarStorageMap;
    1548             : 
    1549             :   // Get the process-specific storage or create one if it's not
    1550             :   // available.
    1551           0 :   if (!processStorage.Get(storageId, &scalarStorage)) {
    1552           0 :     scalarStorage = new KeyedScalarStorageMapType();
    1553           1 :     processStorage.Put(storageId, scalarStorage);
    1554             :   }
    1555             : 
    1556           2 :   if (scalarStorage->Get(aId.id, &scalar)) {
    1557           0 :     *aRet = scalar;
    1558             :     return NS_OK;
    1559             :   }
    1560             : 
    1561           2 :   if (IsExpiredVersion(info.expiration())) {
    1562             :     return NS_ERROR_NOT_AVAILABLE;
    1563             :   }
    1564             : 
    1565             :   // We don't currently support keyed string scalars. Disable them.
    1566           2 :   if (info.kind == nsITelemetry::SCALAR_TYPE_STRING) {
    1567           0 :     MOZ_ASSERT(false, "Keyed string scalars are not currently supported.");
    1568             :     return NS_ERROR_INVALID_ARG;
    1569             :   }
    1570             : 
    1571           4 :   scalar = new KeyedScalar(info.kind);
    1572           2 :   if (!scalar) {
    1573             :     return NS_ERROR_INVALID_ARG;
    1574             :   }
    1575             : 
    1576           2 :   scalarStorage->Put(aId.id, scalar);
    1577           0 :   *aRet = scalar;
    1578             :   return NS_OK;
    1579             : }
    1580             : 
    1581             : /**
    1582             :  * Update the keyed scalar with the provided value. This is used by the JS API.
    1583             :  *
    1584             :  * @param lock Instance of a lock locking gTelemetryHistogramMutex
    1585             :  * @param aName The scalar name.
    1586             :  * @param aKey The key name.
    1587             :  * @param aType The action type for updating the scalar.
    1588             :  * @param aValue The value to use for updating the scalar.
    1589             :  * @param aProcessOverride The process for which the scalar must be updated.
    1590             :  *        This must only be used for GeckoView persistence. It must be
    1591             :  *        set to the ProcessID::Parent for all the other cases.
    1592             :  * @return a ScalarResult error value.
    1593             :  */
    1594             : ScalarResult
    1595           0 : internal_UpdateKeyedScalar(const StaticMutexAutoLock& lock,
    1596             :                            const nsACString& aName, const nsAString& aKey,
    1597             :                            ScalarActionType aType, nsIVariant* aValue,
    1598             :                            ProcessID aProcessOverride = ProcessID::Parent,
    1599             :                            bool aForce = false)
    1600             : {
    1601             :   ScalarKey uniqueId;
    1602           0 :   nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
    1603           0 :   if (NS_FAILED(rv)) {
    1604           0 :     return (rv == NS_ERROR_FAILURE) ?
    1605             :            ScalarResult::NotInitialized : ScalarResult::UnknownScalar;
    1606             :   }
    1607             : 
    1608           0 :   ScalarResult sr = internal_CanRecordScalar(lock, uniqueId, true);
    1609           0 :   if (sr != ScalarResult::Ok) {
    1610           0 :     if (sr == ScalarResult::CannotRecordDataset) {
    1611             :       return ScalarResult::Ok;
    1612             :     }
    1613           0 :     return sr;
    1614             :   }
    1615             : 
    1616             :   // Accumulate in the child process if needed.
    1617           0 :   if (!XRE_IsParentProcess()) {
    1618           0 :     const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
    1619             :     // Convert the nsIVariant to a Variant.
    1620           0 :     mozilla::Maybe<ScalarVariant> variantValue;
    1621           0 :     sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
    1622           0 :     if (sr != ScalarResult::Ok) {
    1623           0 :       MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
    1624             :       return sr;
    1625             :     }
    1626           0 :     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
    1627           0 :       uniqueId.id, uniqueId.dynamic, aKey, aType, variantValue.ref());
    1628           0 :     return ScalarResult::Ok;
    1629             :   }
    1630             : 
    1631           0 :   if (!aForce && internal_IsScalarDeserializing(lock)) {
    1632           0 :     const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
    1633             :     // Convert the nsIVariant to a Variant.
    1634           0 :     mozilla::Maybe<ScalarVariant> variantValue;
    1635           0 :     sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
    1636           0 :     if (sr != ScalarResult::Ok) {
    1637           0 :       MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
    1638             :       return sr;
    1639             :     }
    1640           0 :     internal_RecordKeyedScalarAction(lock,
    1641           0 :                                      uniqueId.id, uniqueId.dynamic,
    1642           0 :                                      aKey, aType, variantValue.ref());
    1643           0 :     return ScalarResult::Ok;
    1644             :   }
    1645             : 
    1646             :   // Finally get the scalar.
    1647           0 :   KeyedScalar* scalar = nullptr;
    1648           0 :   rv = internal_GetKeyedScalarByEnum(lock, uniqueId, aProcessOverride, &scalar);
    1649           0 :   if (NS_FAILED(rv)) {
    1650             :     // Don't throw on expired scalars.
    1651           0 :     if (rv == NS_ERROR_NOT_AVAILABLE) {
    1652             :       return ScalarResult::Ok;
    1653             :     }
    1654           0 :     return ScalarResult::UnknownScalar;
    1655             :   }
    1656             : 
    1657           0 :   if (aType == ScalarActionType::eAdd) {
    1658           0 :     return scalar->AddValue(aKey, aValue);
    1659             :   }
    1660           0 :   if (aType == ScalarActionType::eSet) {
    1661           0 :     return scalar->SetValue(aKey, aValue);
    1662             :   }
    1663             : 
    1664           0 :   return scalar->SetMaximum(aKey, aValue);
    1665             : }
    1666             : 
    1667             : /**
    1668             :  * Helper function to convert an array of |DynamicScalarInfo|
    1669             :  * to |DynamicScalarDefinition| used by the IPC calls.
    1670             :  */
    1671             : void
    1672           0 : internal_DynamicScalarToIPC(const StaticMutexAutoLock& lock,
    1673             :                             const nsTArray<DynamicScalarInfo>& aDynamicScalarInfos,
    1674             :                             nsTArray<DynamicScalarDefinition>& aIPCDefs)
    1675             : {
    1676           5 :   for (auto info : aDynamicScalarInfos) {
    1677           0 :     DynamicScalarDefinition stubDefinition;
    1678           0 :     stubDefinition.type = info.kind;
    1679           1 :     stubDefinition.dataset = info.dataset;
    1680           1 :     stubDefinition.expired = info.mDynamicExpiration;
    1681           0 :     stubDefinition.keyed = info.keyed;
    1682           0 :     stubDefinition.name = info.mDynamicName;
    1683           1 :     aIPCDefs.AppendElement(stubDefinition);
    1684             :   }
    1685           1 : }
    1686             : 
    1687             : /**
    1688             :  * Broadcasts the dynamic scalar definitions to all the other
    1689             :  * content processes.
    1690             :  */
    1691             : void
    1692           0 : internal_BroadcastDefinitions(const StaticMutexAutoLock& lock,
    1693             :                               const nsTArray<DynamicScalarInfo>& scalarInfos)
    1694             : {
    1695           0 :   nsTArray<mozilla::dom::ContentParent*> parents;
    1696           0 :   mozilla::dom::ContentParent::GetAll(parents);
    1697           0 :   if (!parents.Length()) {
    1698           0 :      return;
    1699             :   }
    1700             : 
    1701             :   // Convert the internal scalar representation to a stripped down IPC one.
    1702           0 :   nsTArray<DynamicScalarDefinition> ipcDefinitions;
    1703           0 :   internal_DynamicScalarToIPC(lock, scalarInfos, ipcDefinitions);
    1704             : 
    1705             :   // Broadcast the definitions to the other content processes.
    1706           0 :   for (auto parent : parents) {
    1707           0 :       mozilla::Unused << parent->SendAddDynamicScalars(ipcDefinitions);
    1708             :   }
    1709             : }
    1710             : 
    1711             : void
    1712           0 : internal_RegisterScalars(const StaticMutexAutoLock& lock,
    1713             :                          const nsTArray<DynamicScalarInfo>& scalarInfos)
    1714             : {
    1715             :   // Register the new scalars.
    1716           1 :   if (!gDynamicScalarInfo) {
    1717           2 :     gDynamicScalarInfo = new nsTArray<DynamicScalarInfo>();
    1718             :   }
    1719             : 
    1720           5 :   for (auto scalarInfo : scalarInfos) {
    1721             :     // Allow expiring scalars that were already registered.
    1722           2 :     CharPtrEntryType *existingKey = gScalarNameIDMap.GetEntry(scalarInfo.name());
    1723           1 :     if (existingKey) {
    1724             :       // Change the scalar to expired if needed.
    1725           0 :       if (scalarInfo.mDynamicExpiration && !scalarInfo.builtin) {
    1726           0 :         DynamicScalarInfo& scalarData = (*gDynamicScalarInfo)[existingKey->mData.id];
    1727           0 :         scalarData.mDynamicExpiration = true;
    1728             :       }
    1729           0 :       continue;
    1730             :     }
    1731             : 
    1732           0 :     gDynamicScalarInfo->AppendElement(scalarInfo);
    1733           0 :     uint32_t scalarId = gDynamicScalarInfo->Length() - 1;
    1734           0 :     CharPtrEntryType *entry = gScalarNameIDMap.PutEntry(scalarInfo.name());
    1735           1 :     entry->mData = ScalarKey{scalarId, true};
    1736             :   }
    1737           0 : }
    1738             : 
    1739             : /**
    1740             :  * Creates a snapshot of the desired scalar storage.
    1741             :  * @param {aLock} The proof of lock to access scalar data.
    1742             :  * @param {aScalarsToReflect} The table that will contain the snapshot.
    1743             :  * @param {aDataset} The dataset we're asking the snapshot for.
    1744             :  * @param {aProcessStorage} The scalar storage to take a snapshot of.
    1745             :  * @param {aIsBuiltinDynamic} Whether or not the storage is for dynamic builtin scalars.
    1746             :  * @return NS_OK or the error code describing the failure reason.
    1747             :  */
    1748             : nsresult
    1749           0 : internal_ScalarSnapshotter(const StaticMutexAutoLock& aLock,
    1750             :                            ScalarSnapshotTable& aScalarsToReflect,
    1751             :                            unsigned int aDataset,
    1752             :                            ProcessesScalarsMapType& aProcessStorage,
    1753             :                            bool aIsBuiltinDynamic)
    1754             : {
    1755             :   // Iterate the scalars in aProcessStorage. The storage may contain empty or yet to be
    1756             :   // initialized scalars from all the supported processes.
    1757           0 :   for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
    1758           0 :     ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
    1759           0 :     ScalarTupleArray& processScalars = aScalarsToReflect.GetOrInsert(iter.Key());
    1760             : 
    1761             :     // Are we in the "Dynamic" process?
    1762           0 :     bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
    1763             : 
    1764             :     // Iterate each available child storage.
    1765           0 :     for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
    1766           0 :       ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
    1767             : 
    1768             :       // Get the informations for this scalar.
    1769             :       const BaseScalarInfo& info =
    1770           0 :         internal_GetScalarInfo(aLock, ScalarKey{childIter.Key(),
    1771           0 :                                aIsBuiltinDynamic ? true : isDynamicProcess});
    1772             : 
    1773             :       // Serialize the scalar if it's in the desired dataset.
    1774           0 :       if (IsInDataset(info.dataset, aDataset)) {
    1775             :         // Get the scalar value.
    1776           0 :         nsCOMPtr<nsIVariant> scalarValue;
    1777           0 :         nsresult rv = scalar->GetValue(scalarValue);
    1778           0 :         if (NS_FAILED(rv)) {
    1779           0 :           return rv;
    1780             :         }
    1781             :         // Append it to our list.
    1782           0 :         processScalars.AppendElement(mozilla::MakeTuple(info.name(), scalarValue, info.kind));
    1783             :       }
    1784             :     }
    1785             :   }
    1786           0 :   return NS_OK;
    1787             : }
    1788             : 
    1789             : /**
    1790             :  * Creates a snapshot of the desired keyed scalar storage.
    1791             :  * @param {aLock} The proof of lock to access scalar data.
    1792             :  * @param {aScalarsToReflect} The table that will contain the snapshot.
    1793             :  * @param {aDataset} The dataset we're asking the snapshot for.
    1794             :  * @param {aProcessStorage} The scalar storage to take a snapshot of.
    1795             :  * @param {aIsBuiltinDynamic} Whether or not the storage is for dynamic builtin scalars.
    1796             :  * @return NS_OK or the error code describing the failure reason.
    1797             :  */
    1798             : nsresult
    1799           0 : internal_KeyedScalarSnapshotter(const StaticMutexAutoLock& aLock,
    1800             :                                 KeyedScalarSnapshotTable& aScalarsToReflect,
    1801             :                                 unsigned int aDataset,
    1802             :                                 ProcessesKeyedScalarsMapType& aProcessStorage,
    1803             :                                 bool aIsBuiltinDynamic)
    1804             : {
    1805             :   // Iterate the scalars in aProcessStorage. The storage may contain empty or yet
    1806             :   // to be initialized scalars from all the supported processes.
    1807           0 :   for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
    1808             :     KeyedScalarStorageMapType* scalarStorage =
    1809           0 :       static_cast<KeyedScalarStorageMapType*>(iter.Data());
    1810           0 :     KeyedScalarTupleArray& processScalars = aScalarsToReflect.GetOrInsert(iter.Key());
    1811             : 
    1812             :     // Are we in the "Dynamic" process?
    1813           0 :     bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
    1814             : 
    1815           0 :     for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
    1816           0 :       KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
    1817             : 
    1818             :       // Get the informations for this scalar.
    1819             :       const BaseScalarInfo& info =
    1820           0 :         internal_GetScalarInfo(aLock, ScalarKey{childIter.Key(),
    1821           0 :                                aIsBuiltinDynamic ? true : isDynamicProcess});
    1822             : 
    1823             :       // Serialize the scalar if it's in the desired dataset.
    1824           0 :       if (IsInDataset(info.dataset, aDataset)) {
    1825             :         // Get the keys for this scalar.
    1826           0 :         nsTArray<KeyedScalar::KeyValuePair> scalarKeyedData;
    1827           0 :         nsresult rv = scalar->GetValue(scalarKeyedData);
    1828           0 :         if (NS_FAILED(rv)) {
    1829           0 :           return rv;
    1830             :         }
    1831             :         // Append it to our list.
    1832           0 :         processScalars.AppendElement(
    1833           0 :           mozilla::MakeTuple(info.name(), scalarKeyedData, info.kind));
    1834             :       }
    1835             :     }
    1836             :   }
    1837           0 :   return NS_OK;
    1838             : }
    1839             : 
    1840             : /**
    1841             :  * Helper function to get a snapshot of the scalars.
    1842             :  *
    1843             :  * @param {aLock} The proof of lock to access scalar data.
    1844             :  * @param {aScalarsToReflect} The table that will contain the snapshot.
    1845             :  * @param {aDataset} The dataset we're asking the snapshot for.
    1846             :  * @param {aClearScalars} Whether or not to clear the scalar storage.
    1847             :  * @return NS_OK or the error code describing the failure reason.
    1848             :  */
    1849             : nsresult
    1850           0 : internal_GetScalarSnapshot(const StaticMutexAutoLock& aLock,
    1851             :                            ScalarSnapshotTable& aScalarsToReflect,
    1852             :                            unsigned int aDataset, bool aClearScalars)
    1853             : {
    1854             :   // Take a snapshot of the scalars.
    1855             :   nsresult rv = internal_ScalarSnapshotter(aLock,
    1856             :                                            aScalarsToReflect,
    1857             :                                            aDataset,
    1858             :                                            gScalarStorageMap,
    1859           0 :                                            false /*aIsBuiltinDynamic*/);
    1860           0 :   if (NS_FAILED(rv)) {
    1861             :     return rv;
    1862             :   }
    1863             : 
    1864             :   // And a snapshot of the dynamic builtin ones.
    1865             :   rv = internal_ScalarSnapshotter(aLock,
    1866             :                                   aScalarsToReflect,
    1867             :                                   aDataset,
    1868             :                                   gDynamicBuiltinScalarStorageMap,
    1869           0 :                                   true /*aIsBuiltinDynamic*/);
    1870           0 :   if (NS_FAILED(rv)) {
    1871             :     return rv;
    1872             :   }
    1873             : 
    1874           0 :   if (aClearScalars) {
    1875             :     // The map already takes care of freeing the allocated memory.
    1876           0 :     gScalarStorageMap.Clear();
    1877             :     gDynamicBuiltinScalarStorageMap.Clear();
    1878             :   }
    1879             : 
    1880             :   return NS_OK;
    1881             : }
    1882             : 
    1883             : /**
    1884             :  * Helper function to get a snapshot of the keyed scalars.
    1885             :  *
    1886             :  * @param {aLock} The proof of lock to access scalar data.
    1887             :  * @param {aScalarsToReflect} The table that will contain the snapshot.
    1888             :  * @param {aDataset} The dataset we're asking the snapshot for.
    1889             :  * @param {aClearScalars} Whether or not to clear the scalar storage.
    1890             :  * @return NS_OK or the error code describing the failure reason.
    1891             :  */
    1892             : nsresult
    1893           0 : internal_GetKeyedScalarSnapshot(const StaticMutexAutoLock& aLock,
    1894             :                                 KeyedScalarSnapshotTable& aScalarsToReflect,
    1895             :                                 unsigned int aDataset, bool aClearScalars)
    1896             : {
    1897             :   // Take a snapshot of the scalars.
    1898             :   nsresult rv = internal_KeyedScalarSnapshotter(aLock,
    1899             :                                                 aScalarsToReflect,
    1900             :                                                 aDataset,
    1901             :                                                 gKeyedScalarStorageMap,
    1902           0 :                                                 false /*aIsBuiltinDynamic*/);
    1903           0 :   if (NS_FAILED(rv)) {
    1904             :     return rv;
    1905             :   }
    1906             : 
    1907             :   // And a snapshot of the dynamic builtin ones.
    1908             :   rv = internal_KeyedScalarSnapshotter(aLock,
    1909             :                                        aScalarsToReflect,
    1910             :                                        aDataset,
    1911             :                                        gDynamicBuiltinKeyedScalarStorageMap,
    1912           0 :                                        true /*aIsBuiltinDynamic*/);
    1913           0 :   if (NS_FAILED(rv)) {
    1914             :     return rv;
    1915             :   }
    1916             : 
    1917           0 :   if (aClearScalars) {
    1918             :     // The map already takes care of freeing the allocated memory.
    1919           0 :     gKeyedScalarStorageMap.Clear();
    1920             :     gDynamicBuiltinKeyedScalarStorageMap.Clear();
    1921             :   }
    1922             : 
    1923             :   return NS_OK;
    1924             : }
    1925             : 
    1926             : } // namespace
    1927             : 
    1928             : // helpers for recording/applying scalar operations
    1929             : namespace {
    1930             : 
    1931             : void
    1932           0 : internal_ApplyScalarActions(const StaticMutexAutoLock& lock,
    1933             :                             const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions,
    1934             :                             const mozilla::Maybe<ProcessID>& aProcessType = Nothing())
    1935             : {
    1936           0 :   if (!internal_CanRecordBase(lock)) {
    1937             :     return;
    1938             :   }
    1939             : 
    1940           0 :   for (auto& upd : aScalarActions) {
    1941           0 :     ScalarKey uniqueId{upd.mId, upd.mDynamic};
    1942           0 :     if (NS_WARN_IF(!internal_IsValidId(lock, uniqueId))) {
    1943           0 :       MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    1944           0 :       continue;
    1945             :     }
    1946             : 
    1947           0 :     if (internal_IsKeyedScalar(lock, uniqueId)) {
    1948             :       continue;
    1949             :     }
    1950             : 
    1951             :     // Are we allowed to record this scalar? We don't need to check for
    1952             :     // allowed processes here, that's taken care of when recording
    1953             :     // in child processes.
    1954           0 :     if (!internal_CanRecordForScalarID(lock, uniqueId)) {
    1955             :       continue;
    1956             :     }
    1957             : 
    1958             :     // Either we got passed a process type or it was explicitely set on the recorded action.
    1959             :     // It should never happen that it is set to an invalid value (such as ProcessID::Count)
    1960           0 :     ProcessID processType = aProcessType.valueOr(upd.mProcessType);
    1961           0 :     MOZ_ASSERT(processType != ProcessID::Count);
    1962             : 
    1963             :     // Refresh the data in the parent process with the data coming from the child
    1964             :     // processes.
    1965           0 :     ScalarBase* scalar = nullptr;
    1966             :     nsresult rv = internal_GetScalarByEnum(lock, uniqueId, processType,
    1967           0 :                                            &scalar);
    1968           0 :     if (NS_FAILED(rv)) {
    1969           0 :       NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
    1970             :       continue;
    1971             :     }
    1972             : 
    1973           0 :     if (upd.mData.isNothing()) {
    1974           0 :       MOZ_ASSERT(false, "There is no data in the ScalarActionType.");
    1975             :       continue;
    1976             :     }
    1977             : 
    1978             :     // Get the type of this scalar from the scalar ID. We already checked
    1979             :     // for its validity a few lines above.
    1980           0 :     const uint32_t scalarType = internal_GetScalarInfo(lock, uniqueId).kind;
    1981             : 
    1982             :     // Extract the data from the mozilla::Variant.
    1983           0 :     switch (upd.mActionType)
    1984             :     {
    1985             :       case ScalarActionType::eSet:
    1986             :         {
    1987           0 :           switch (scalarType)
    1988             :           {
    1989             :             case nsITelemetry::SCALAR_TYPE_COUNT:
    1990           0 :               scalar->SetValue(upd.mData->as<uint32_t>());
    1991             :               break;
    1992             :             case nsITelemetry::SCALAR_TYPE_BOOLEAN:
    1993           0 :               scalar->SetValue(upd.mData->as<bool>());
    1994             :               break;
    1995             :             case nsITelemetry::SCALAR_TYPE_STRING:
    1996           0 :               scalar->SetValue(upd.mData->as<nsString>());
    1997             :               break;
    1998             :           }
    1999             :           break;
    2000             :         }
    2001             :       case ScalarActionType::eAdd:
    2002             :         {
    2003           0 :           if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
    2004           0 :             NS_WARNING("Attempting to add on a non count scalar.");
    2005             :             continue;
    2006             :           }
    2007             :           // We only support adding uint32_t.
    2008           0 :           scalar->AddValue(upd.mData->as<uint32_t>());
    2009             :           break;
    2010             :         }
    2011             :       case ScalarActionType::eSetMaximum:
    2012             :         {
    2013           0 :           if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
    2014           0 :             NS_WARNING("Attempting to add on a non count scalar.");
    2015             :             continue;
    2016             :           }
    2017             :           // We only support SetMaximum on uint32_t.
    2018           0 :           scalar->SetMaximum(upd.mData->as<uint32_t>());
    2019             :           break;
    2020             :         }
    2021             :       default:
    2022           0 :         NS_WARNING("Unsupported action coming from scalar child updates.");
    2023             :     }
    2024             :   }
    2025             : }
    2026             : 
    2027             : void
    2028           0 : internal_ApplyKeyedScalarActions(const StaticMutexAutoLock& lock,
    2029             :                                  const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions,
    2030             :                                  const mozilla::Maybe<ProcessID>& aProcessType = Nothing())
    2031             : {
    2032           0 :   if (!internal_CanRecordBase(lock)) {
    2033             :     return;
    2034             :   }
    2035             : 
    2036           0 :   for (auto& upd : aScalarActions) {
    2037           0 :     ScalarKey uniqueId{upd.mId, upd.mDynamic};
    2038           0 :     if (NS_WARN_IF(!internal_IsValidId(lock, uniqueId))) {
    2039           0 :       MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    2040           0 :       continue;
    2041             :     }
    2042             : 
    2043           0 :     if (!internal_IsKeyedScalar(lock, uniqueId)) {
    2044             :       continue;
    2045             :     }
    2046             : 
    2047             :     // Are we allowed to record this scalar? We don't need to check for
    2048             :     // allowed processes here, that's taken care of when recording
    2049             :     // in child processes.
    2050           0 :     if (!internal_CanRecordForScalarID(lock, uniqueId)) {
    2051             :       continue;
    2052             :     }
    2053             : 
    2054             :     // Either we got passed a process type or it was explicitely set on the recorded action.
    2055             :     // It should never happen that it is set to an invalid value (such as ProcessID::Count)
    2056           0 :     ProcessID processType = aProcessType.valueOr(upd.mProcessType);
    2057           0 :     MOZ_ASSERT(processType != ProcessID::Count);
    2058             : 
    2059             :     // Refresh the data in the parent process with the data coming from the child
    2060             :     // processes.
    2061           0 :     KeyedScalar* scalar = nullptr;
    2062             :     nsresult rv = internal_GetKeyedScalarByEnum(lock, uniqueId, processType,
    2063           0 :                                                 &scalar);
    2064           0 :     if (NS_FAILED(rv)) {
    2065           0 :       NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
    2066             :       continue;
    2067             :     }
    2068             : 
    2069           0 :     if (upd.mData.isNothing()) {
    2070           0 :       MOZ_ASSERT(false, "There is no data in the KeyedScalarAction.");
    2071             :       continue;
    2072             :     }
    2073             : 
    2074             :     // Get the type of this scalar from the scalar ID. We already checked
    2075             :     // for its validity a few lines above.
    2076           0 :     const uint32_t scalarType = internal_GetScalarInfo(lock, uniqueId).kind;
    2077             : 
    2078             :     // Extract the data from the mozilla::Variant.
    2079           0 :     switch (upd.mActionType)
    2080             :     {
    2081             :       case ScalarActionType::eSet:
    2082             :         {
    2083           0 :           switch (scalarType)
    2084             :           {
    2085             :             case nsITelemetry::SCALAR_TYPE_COUNT:
    2086           0 :               scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
    2087             :               break;
    2088             :             case nsITelemetry::SCALAR_TYPE_BOOLEAN:
    2089           0 :               scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<bool>());
    2090             :               break;
    2091             :             default:
    2092           0 :               NS_WARNING("Unsupported type coming from scalar child updates.");
    2093             :           }
    2094             :           break;
    2095             :         }
    2096             :       case ScalarActionType::eAdd:
    2097             :         {
    2098           0 :           if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
    2099           0 :             NS_WARNING("Attempting to add on a non count scalar.");
    2100             :             continue;
    2101             :           }
    2102             :           // We only support adding on uint32_t.
    2103           0 :           scalar->AddValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
    2104             :           break;
    2105             :         }
    2106             :       case ScalarActionType::eSetMaximum:
    2107             :         {
    2108           0 :           if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
    2109           0 :             NS_WARNING("Attempting to add on a non count scalar.");
    2110             :             continue;
    2111             :           }
    2112             :           // We only support SetMaximum on uint32_t.
    2113           0 :           scalar->SetMaximum(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
    2114             :           break;
    2115             :         }
    2116             :       default:
    2117           0 :         NS_WARNING("Unsupported action coming from keyed scalar child updates.");
    2118             :     }
    2119             :   }
    2120             : }
    2121             : 
    2122             : void
    2123           0 : internal_ApplyPendingOperations(const StaticMutexAutoLock& lock)
    2124             : {
    2125           0 :   if (gScalarsActions && gScalarsActions->Length() > 0) {
    2126           0 :     internal_ApplyScalarActions(lock, *gScalarsActions);
    2127           0 :     gScalarsActions->Clear();
    2128             :   }
    2129             : 
    2130           0 :   if (gKeyedScalarsActions && gKeyedScalarsActions->Length() > 0) {
    2131           0 :     internal_ApplyKeyedScalarActions(lock, *gKeyedScalarsActions);
    2132           0 :     gKeyedScalarsActions->Clear();
    2133             :   }
    2134             : 
    2135             :   // After all pending operations are applied deserialization is done
    2136           0 :   gIsDeserializing = false;
    2137           0 : }
    2138             : 
    2139             : } // namespace
    2140             : 
    2141             : ////////////////////////////////////////////////////////////////////////
    2142             : ////////////////////////////////////////////////////////////////////////
    2143             : //
    2144             : // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryScalars::
    2145             : 
    2146             : // This is a StaticMutex rather than a plain Mutex (1) so that
    2147             : // it gets initialised in a thread-safe manner the first time
    2148             : // it is used, and (2) because it is never de-initialised, and
    2149             : // a normal Mutex would show up as a leak in BloatView.  StaticMutex
    2150             : // also has the "OffTheBooks" property, so it won't show as a leak
    2151             : // in BloatView.
    2152             : // Another reason to use a StaticMutex instead of a plain Mutex is
    2153             : // that, due to the nature of Telemetry, we cannot rely on having a
    2154             : // mutex initialized in InitializeGlobalState. Unfortunately, we
    2155             : // cannot make sure that no other function is called before this point.
    2156           1 : static StaticMutex gTelemetryScalarsMutex;
    2157             : 
    2158             : void
    2159           1 : TelemetryScalar::InitializeGlobalState(bool aCanRecordBase, bool aCanRecordExtended)
    2160             : {
    2161           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2162           0 :   MOZ_ASSERT(!gInitDone, "TelemetryScalar::InitializeGlobalState "
    2163             :              "may only be called once");
    2164             : 
    2165           0 :   gCanRecordBase = aCanRecordBase;
    2166           1 :   gCanRecordExtended = aCanRecordExtended;
    2167             : 
    2168             :   // Populate the static scalar name->id cache. Note that the scalar names are
    2169             :   // statically allocated and come from the automatically generated TelemetryScalarData.h.
    2170           0 :   uint32_t scalarCount = static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
    2171           0 :   for (uint32_t i = 0; i < scalarCount; i++) {
    2172           0 :     CharPtrEntryType *entry = gScalarNameIDMap.PutEntry(gScalars[i].name());
    2173           0 :     entry->mData = ScalarKey{i, false};
    2174             :   }
    2175             : 
    2176             :   // To summarize dynamic events we need a dynamic scalar.
    2177             :   const nsTArray<DynamicScalarInfo> initialDynamicScalars({
    2178             :     DynamicScalarInfo{
    2179             :       nsITelemetry::SCALAR_TYPE_COUNT,
    2180             :       true /* recordOnRelease */,
    2181             :       false /* expired */,
    2182           2 :       nsAutoCString("telemetry.dynamic_event_counts"),
    2183             :       true /* keyed */,
    2184             :       false /* built-in */,
    2185             :     },
    2186           0 :   });
    2187           0 :   internal_RegisterScalars(locker, initialDynamicScalars);
    2188             : 
    2189           1 :   gInitDone = true;
    2190           0 : }
    2191             : 
    2192             : void
    2193           0 : TelemetryScalar::DeInitializeGlobalState()
    2194             : {
    2195           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2196           0 :   gCanRecordBase = false;
    2197           0 :   gCanRecordExtended = false;
    2198           0 :   gScalarNameIDMap.Clear();
    2199           0 :   gScalarStorageMap.Clear();
    2200           0 :   gKeyedScalarStorageMap.Clear();
    2201           0 :   gDynamicBuiltinScalarStorageMap.Clear();
    2202           0 :   gDynamicBuiltinKeyedScalarStorageMap.Clear();
    2203           0 :   gDynamicScalarInfo = nullptr;
    2204           0 :   gInitDone = false;
    2205           0 : }
    2206             : 
    2207             : void
    2208           0 : TelemetryScalar::DeserializationStarted()
    2209             : {
    2210           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2211           0 :   gIsDeserializing = true;
    2212           0 : }
    2213             : 
    2214             : void
    2215           0 : TelemetryScalar::ApplyPendingOperations()
    2216             : {
    2217           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2218           0 :   internal_ApplyPendingOperations(locker);
    2219           0 : }
    2220             : 
    2221             : void
    2222           0 : TelemetryScalar::SetCanRecordBase(bool b)
    2223             : {
    2224           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2225           0 :   gCanRecordBase = b;
    2226           0 : }
    2227             : 
    2228             : void
    2229           1 : TelemetryScalar::SetCanRecordExtended(bool b) {
    2230           2 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2231           1 :   gCanRecordExtended = b;
    2232           0 : }
    2233             : 
    2234             : /**
    2235             :  * Adds the value to the given scalar.
    2236             :  *
    2237             :  * @param aName The scalar name.
    2238             :  * @param aVal The numeric value to add to the scalar.
    2239             :  * @param aCx The JS context.
    2240             :  * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
    2241             :  *         a warning level message is printed in the browser console.
    2242             :  */
    2243             : nsresult
    2244           1 : TelemetryScalar::Add(const nsACString& aName, JS::HandleValue aVal, JSContext* aCx)
    2245             : {
    2246             :   // Unpack the aVal to nsIVariant. This uses the JS context.
    2247           2 :   nsCOMPtr<nsIVariant> unpackedVal;
    2248             :   nsresult rv =
    2249           2 :     nsContentUtils::XPConnect()->JSToVariant(aCx, aVal,  getter_AddRefs(unpackedVal));
    2250           1 :   if (NS_FAILED(rv)) {
    2251           0 :     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
    2252           0 :     return NS_OK;
    2253             :   }
    2254             : 
    2255             :   ScalarResult sr;
    2256             :   {
    2257           2 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2258           1 :     sr = internal_UpdateScalar(locker, aName, ScalarActionType::eAdd,
    2259           1 :                                unpackedVal);
    2260             :   }
    2261             : 
    2262             :   // Warn the user about the error if we need to.
    2263           0 :   if (sr != ScalarResult::Ok) {
    2264           0 :     internal_LogScalarError(aName, sr);
    2265             :   }
    2266             : 
    2267             :   return NS_OK;
    2268             : }
    2269             : 
    2270             : /**
    2271             :  * Adds the value to the given scalar.
    2272             :  *
    2273             :  * @param aName The scalar name.
    2274             :  * @param aKey The key name.
    2275             :  * @param aVal The numeric value to add to the scalar.
    2276             :  * @param aCx The JS context.
    2277             :  * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
    2278             :  *         a warning level message is printed in the browser console.
    2279             :  */
    2280             : nsresult
    2281           0 : TelemetryScalar::Add(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
    2282             :                      JSContext* aCx)
    2283             : {
    2284             :   // Unpack the aVal to nsIVariant. This uses the JS context.
    2285           0 :   nsCOMPtr<nsIVariant> unpackedVal;
    2286             :   nsresult rv =
    2287           0 :     nsContentUtils::XPConnect()->JSToVariant(aCx, aVal,  getter_AddRefs(unpackedVal));
    2288           0 :   if (NS_FAILED(rv)) {
    2289           0 :     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
    2290           0 :     return NS_OK;
    2291             :   }
    2292             : 
    2293             :   ScalarResult sr;
    2294             :   {
    2295           0 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2296           0 :     sr = internal_UpdateKeyedScalar(locker, aName, aKey,
    2297             :                                     ScalarActionType::eAdd,
    2298           0 :                                     unpackedVal);
    2299             :   }
    2300             : 
    2301             :   // Warn the user about the error if we need to.
    2302           0 :   if (sr != ScalarResult::Ok) {
    2303           0 :     internal_LogScalarError(aName, sr);
    2304             :   }
    2305             : 
    2306             :   return NS_OK;
    2307             : }
    2308             : 
    2309             : /**
    2310             :  * Adds the value to the given scalar.
    2311             :  *
    2312             :  * @param aId The scalar enum id.
    2313             :  * @param aVal The numeric value to add to the scalar.
    2314             :  */
    2315             : void
    2316           0 : TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
    2317             : {
    2318           0 :   if (NS_WARN_IF(!IsValidEnumId(aId))) {
    2319           0 :     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    2320             :     return;
    2321             :   }
    2322             : 
    2323           0 :   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
    2324           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2325             : 
    2326           0 :   if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
    2327             :     // We can't record this scalar. Bail out.
    2328           0 :     return;
    2329             :   }
    2330             : 
    2331             :   // Accumulate in the child process if needed.
    2332           0 :   if (!XRE_IsParentProcess()) {
    2333           0 :     TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
    2334             :                                                      ScalarActionType::eAdd,
    2335           0 :                                                      ScalarVariant(aValue));
    2336           0 :     return;
    2337             :   }
    2338             : 
    2339           0 :   if (internal_IsScalarDeserializing(locker)) {
    2340           0 :     internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
    2341             :                                 ScalarActionType::eAdd,
    2342           0 :                                 ScalarVariant(aValue));
    2343           0 :     return;
    2344             :   }
    2345             : 
    2346           0 :   ScalarBase* scalar = nullptr;
    2347             :   nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
    2348           0 :                                          &scalar);
    2349           0 :   if (NS_FAILED(rv)) {
    2350             :     return;
    2351             :   }
    2352             : 
    2353           0 :   scalar->AddValue(aValue);
    2354             : }
    2355             : 
    2356             : /**
    2357             :  * Adds the value to the given keyed scalar.
    2358             :  *
    2359             :  * @param aId The scalar enum id.
    2360             :  * @param aKey The key name.
    2361             :  * @param aVal The numeric value to add to the scalar.
    2362             :  */
    2363             : void
    2364           1 : TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
    2365             :                      uint32_t aValue)
    2366             : {
    2367           0 :   if (NS_WARN_IF(!IsValidEnumId(aId))) {
    2368           0 :     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    2369             :     return;
    2370             :   }
    2371             : 
    2372           1 :   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
    2373           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2374             : 
    2375           1 :   if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
    2376             :     // We can't record this scalar. Bail out.
    2377           0 :     return;
    2378             :   }
    2379             : 
    2380             :   // Accumulate in the child process if needed.
    2381           1 :   if (!XRE_IsParentProcess()) {
    2382           0 :     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
    2383           0 :       aKey, ScalarActionType::eAdd, ScalarVariant(aValue));
    2384           0 :     return;
    2385             :   }
    2386             : 
    2387           2 :   if (internal_IsScalarDeserializing(locker)) {
    2388           0 :     internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
    2389             :                                      aKey,
    2390             :                                      ScalarActionType::eAdd,
    2391           0 :                                      ScalarVariant(aValue));
    2392           0 :     return;
    2393             :   }
    2394             : 
    2395           1 :   KeyedScalar* scalar = nullptr;
    2396             :   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
    2397             :                                               ProcessID::Parent,
    2398           1 :                                               &scalar);
    2399           1 :   if (NS_FAILED(rv)) {
    2400             :     return;
    2401             :   }
    2402             : 
    2403           1 :   scalar->AddValue(aKey, aValue);
    2404             : }
    2405             : 
    2406             : /**
    2407             :  * Sets the scalar to the given value.
    2408             :  *
    2409             :  * @param aName The scalar name.
    2410             :  * @param aVal The value to set the scalar to.
    2411             :  * @param aCx The JS context.
    2412             :  * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
    2413             :  *         a warning level message is printed in the browser console.
    2414             :  */
    2415             : nsresult
    2416           2 : TelemetryScalar::Set(const nsACString& aName, JS::HandleValue aVal, JSContext* aCx)
    2417             : {
    2418             :   // Unpack the aVal to nsIVariant. This uses the JS context.
    2419           4 :   nsCOMPtr<nsIVariant> unpackedVal;
    2420             :   nsresult rv =
    2421           4 :     nsContentUtils::XPConnect()->JSToVariant(aCx, aVal,  getter_AddRefs(unpackedVal));
    2422           2 :   if (NS_FAILED(rv)) {
    2423           0 :     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
    2424           0 :     return NS_OK;
    2425             :   }
    2426             : 
    2427             :   ScalarResult sr;
    2428             :   {
    2429           4 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2430           2 :     sr = internal_UpdateScalar(locker, aName, ScalarActionType::eSet,
    2431           2 :                                unpackedVal);
    2432             :   }
    2433             : 
    2434             :   // Warn the user about the error if we need to.
    2435           0 :   if (sr != ScalarResult::Ok) {
    2436           0 :     internal_LogScalarError(aName, sr);
    2437             :   }
    2438             : 
    2439             :   return NS_OK;
    2440             : }
    2441             : 
    2442             : /**
    2443             :  * Sets the keyed scalar to the given value.
    2444             :  *
    2445             :  * @param aName The scalar name.
    2446             :  * @param aKey The key name.
    2447             :  * @param aVal The value to set the scalar to.
    2448             :  * @param aCx The JS context.
    2449             :  * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
    2450             :  *         a warning level message is printed in the browser console.
    2451             :  */
    2452             : nsresult
    2453           0 : TelemetryScalar::Set(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
    2454             :                      JSContext* aCx)
    2455             : {
    2456             :   // Unpack the aVal to nsIVariant. This uses the JS context.
    2457           0 :   nsCOMPtr<nsIVariant> unpackedVal;
    2458             :   nsresult rv =
    2459           0 :     nsContentUtils::XPConnect()->JSToVariant(aCx, aVal,  getter_AddRefs(unpackedVal));
    2460           0 :   if (NS_FAILED(rv)) {
    2461           0 :     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
    2462           0 :     return NS_OK;
    2463             :   }
    2464             : 
    2465             :   ScalarResult sr;
    2466             :   {
    2467           0 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2468           0 :     sr = internal_UpdateKeyedScalar(locker, aName, aKey,
    2469             :                                     ScalarActionType::eSet,
    2470           0 :                                     unpackedVal);
    2471             :   }
    2472             : 
    2473             :   // Warn the user about the error if we need to.
    2474           0 :   if (sr != ScalarResult::Ok) {
    2475           0 :     internal_LogScalarError(aName, sr);
    2476             :   }
    2477             : 
    2478             :   return NS_OK;
    2479             : }
    2480             : 
    2481             : /**
    2482             :  * Sets the scalar to the given numeric value.
    2483             :  *
    2484             :  * @param aId The scalar enum id.
    2485             :  * @param aValue The numeric, unsigned value to set the scalar to.
    2486             :  */
    2487             : void
    2488           0 : TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
    2489             : {
    2490           0 :   if (NS_WARN_IF(!IsValidEnumId(aId))) {
    2491           0 :     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    2492             :     return;
    2493             :   }
    2494             : 
    2495           0 :   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
    2496           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2497             : 
    2498           0 :   if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
    2499             :     // We can't record this scalar. Bail out.
    2500           0 :     return;
    2501             :   }
    2502             : 
    2503             :   // Accumulate in the child process if needed.
    2504           0 :   if (!XRE_IsParentProcess()) {
    2505           0 :     TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
    2506             :                                                      ScalarActionType::eSet,
    2507           0 :                                                      ScalarVariant(aValue));
    2508           0 :     return;
    2509             :   }
    2510             : 
    2511           0 :   if (internal_IsScalarDeserializing(locker)) {
    2512           0 :     internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
    2513             :                                 ScalarActionType::eSet,
    2514           0 :                                 ScalarVariant(aValue));
    2515           0 :     return;
    2516             :   }
    2517             : 
    2518           0 :   ScalarBase* scalar = nullptr;
    2519             :   nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
    2520           0 :                                          &scalar);
    2521           0 :   if (NS_FAILED(rv)) {
    2522             :     return;
    2523             :   }
    2524             : 
    2525           0 :   scalar->SetValue(aValue);
    2526             : }
    2527             : 
    2528             : /**
    2529             :  * Sets the scalar to the given string value.
    2530             :  *
    2531             :  * @param aId The scalar enum id.
    2532             :  * @param aValue The string value to set the scalar to.
    2533             :  */
    2534             : void
    2535           0 : TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aValue)
    2536             : {
    2537           0 :   if (NS_WARN_IF(!IsValidEnumId(aId))) {
    2538           0 :     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    2539             :     return;
    2540             :   }
    2541             : 
    2542           0 :   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
    2543           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2544             : 
    2545           0 :   if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
    2546             :     // We can't record this scalar. Bail out.
    2547           0 :     return;
    2548             :   }
    2549             : 
    2550             :   // Accumulate in the child process if needed.
    2551           0 :   if (!XRE_IsParentProcess()) {
    2552           0 :     TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
    2553             :                                                      ScalarActionType::eSet,
    2554           0 :                                                      ScalarVariant(nsString(aValue)));
    2555           0 :     return;
    2556             :   }
    2557             : 
    2558           0 :   if (internal_IsScalarDeserializing(locker)) {
    2559           0 :     internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
    2560             :                                 ScalarActionType::eSet,
    2561           0 :                                 ScalarVariant(nsString(aValue)));
    2562           0 :     return;
    2563             :   }
    2564             : 
    2565           0 :   ScalarBase* scalar = nullptr;
    2566             :   nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
    2567           0 :                                          &scalar);
    2568           0 :   if (NS_FAILED(rv)) {
    2569             :     return;
    2570             :   }
    2571             : 
    2572           0 :   scalar->SetValue(aValue);
    2573             : }
    2574             : 
    2575             : /**
    2576             :  * Sets the scalar to the given boolean value.
    2577             :  *
    2578             :  * @param aId The scalar enum id.
    2579             :  * @param aValue The boolean value to set the scalar to.
    2580             :  */
    2581             : void
    2582           2 : TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, bool aValue)
    2583             : {
    2584           0 :   if (NS_WARN_IF(!IsValidEnumId(aId))) {
    2585           0 :     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    2586             :     return;
    2587             :   }
    2588             : 
    2589           0 :   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
    2590           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2591             : 
    2592           2 :   if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
    2593             :     // We can't record this scalar. Bail out.
    2594           0 :     return;
    2595             :   }
    2596             : 
    2597             :   // Accumulate in the child process if needed.
    2598           2 :   if (!XRE_IsParentProcess()) {
    2599           0 :     TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
    2600             :                                                      ScalarActionType::eSet,
    2601           0 :                                                      ScalarVariant(aValue));
    2602           0 :     return;
    2603             :   }
    2604             : 
    2605           0 :   if (internal_IsScalarDeserializing(locker)) {
    2606           0 :     internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
    2607             :                                 ScalarActionType::eSet,
    2608           0 :                                 ScalarVariant(aValue));
    2609           0 :     return;
    2610             :   }
    2611             : 
    2612           2 :   ScalarBase* scalar = nullptr;
    2613             :   nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
    2614           0 :                                          &scalar);
    2615           2 :   if (NS_FAILED(rv)) {
    2616             :     return;
    2617             :   }
    2618             : 
    2619           0 :   scalar->SetValue(aValue);
    2620             : }
    2621             : 
    2622             : /**
    2623             :  * Sets the keyed scalar to the given numeric value.
    2624             :  *
    2625             :  * @param aId The scalar enum id.
    2626             :  * @param aKey The scalar key.
    2627             :  * @param aValue The numeric, unsigned value to set the scalar to.
    2628             :  */
    2629             : void
    2630           0 : TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
    2631             :                      uint32_t aValue)
    2632             : {
    2633           0 :   if (NS_WARN_IF(!IsValidEnumId(aId))) {
    2634           0 :     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    2635             :     return;
    2636             :   }
    2637             : 
    2638           0 :   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
    2639           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2640             : 
    2641           0 :   if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
    2642             :     // We can't record this scalar. Bail out.
    2643           0 :     return;
    2644             :   }
    2645             : 
    2646             :   // Accumulate in the child process if needed.
    2647           0 :   if (!XRE_IsParentProcess()) {
    2648           0 :     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
    2649           0 :       aKey, ScalarActionType::eSet, ScalarVariant(aValue));
    2650           0 :     return;
    2651             :   }
    2652             : 
    2653           0 :   if (internal_IsScalarDeserializing(locker)) {
    2654           0 :     internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
    2655             :                                      aKey,
    2656             :                                      ScalarActionType::eSet,
    2657           0 :                                      ScalarVariant(aValue));
    2658           0 :     return;
    2659             :   }
    2660             : 
    2661           0 :   KeyedScalar* scalar = nullptr;
    2662             :   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
    2663             :                                               ProcessID::Parent,
    2664           0 :                                               &scalar);
    2665           0 :   if (NS_FAILED(rv)) {
    2666             :     return;
    2667             :   }
    2668             : 
    2669           0 :   scalar->SetValue(aKey, aValue);
    2670             : }
    2671             : 
    2672             : /**
    2673             :  * Sets the scalar to the given boolean value.
    2674             :  *
    2675             :  * @param aId The scalar enum id.
    2676             :  * @param aKey The scalar key.
    2677             :  * @param aValue The boolean value to set the scalar to.
    2678             :  */
    2679             : void
    2680           1 : TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
    2681             :                      bool aValue)
    2682             : {
    2683           0 :   if (NS_WARN_IF(!IsValidEnumId(aId))) {
    2684           0 :     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    2685             :     return;
    2686             :   }
    2687             : 
    2688           1 :   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
    2689           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2690             : 
    2691           1 :   if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
    2692             :     // We can't record this scalar. Bail out.
    2693           0 :     return;
    2694             :   }
    2695             : 
    2696             :   // Accumulate in the child process if needed.
    2697           1 :   if (!XRE_IsParentProcess()) {
    2698           0 :     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
    2699           0 :       aKey, ScalarActionType::eSet, ScalarVariant(aValue));
    2700           0 :     return;
    2701             :   }
    2702             : 
    2703           2 :   if (internal_IsScalarDeserializing(locker)) {
    2704           0 :     internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
    2705             :                                      aKey,
    2706             :                                      ScalarActionType::eSet,
    2707           0 :                                      ScalarVariant(aValue));
    2708           0 :     return;
    2709             :   }
    2710             : 
    2711           1 :   KeyedScalar* scalar = nullptr;
    2712             :   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
    2713             :                                               ProcessID::Parent,
    2714           1 :                                               &scalar);
    2715           1 :   if (NS_FAILED(rv)) {
    2716             :     return;
    2717             :   }
    2718             : 
    2719           1 :   scalar->SetValue(aKey, aValue);
    2720             : }
    2721             : 
    2722             : /**
    2723             :  * Sets the scalar to the maximum of the current and the passed value.
    2724             :  *
    2725             :  * @param aName The scalar name.
    2726             :  * @param aVal The numeric value to set the scalar to.
    2727             :  * @param aCx The JS context.
    2728             :  * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
    2729             :  *         a warning level message is printed in the browser console.
    2730             :  */
    2731             : nsresult
    2732           2 : TelemetryScalar::SetMaximum(const nsACString& aName, JS::HandleValue aVal, JSContext* aCx)
    2733             : {
    2734             :   // Unpack the aVal to nsIVariant. This uses the JS context.
    2735           4 :   nsCOMPtr<nsIVariant> unpackedVal;
    2736             :   nsresult rv =
    2737           4 :     nsContentUtils::XPConnect()->JSToVariant(aCx, aVal,  getter_AddRefs(unpackedVal));
    2738           2 :   if (NS_FAILED(rv)) {
    2739           0 :     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
    2740           0 :     return NS_OK;
    2741             :   }
    2742             : 
    2743             :   ScalarResult sr;
    2744             :   {
    2745           4 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2746           2 :     sr = internal_UpdateScalar(locker, aName, ScalarActionType::eSetMaximum,
    2747           2 :                                unpackedVal);
    2748             :   }
    2749             : 
    2750             :   // Warn the user about the error if we need to.
    2751           0 :   if (sr != ScalarResult::Ok) {
    2752           0 :     internal_LogScalarError(aName, sr);
    2753             :   }
    2754             : 
    2755             :   return NS_OK;
    2756             : }
    2757             : 
    2758             : /**
    2759             :  * Sets the scalar to the maximum of the current and the passed value.
    2760             :  *
    2761             :  * @param aName The scalar name.
    2762             :  * @param aKey The key name.
    2763             :  * @param aVal The numeric value to set the scalar to.
    2764             :  * @param aCx The JS context.
    2765             :  * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
    2766             :  *         a warning level message is printed in the browser console.
    2767             :  */
    2768             : nsresult
    2769           0 : TelemetryScalar::SetMaximum(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
    2770             :                             JSContext* aCx)
    2771             : {
    2772             :   // Unpack the aVal to nsIVariant. This uses the JS context.
    2773           0 :   nsCOMPtr<nsIVariant> unpackedVal;
    2774             :   nsresult rv =
    2775           0 :     nsContentUtils::XPConnect()->JSToVariant(aCx, aVal,  getter_AddRefs(unpackedVal));
    2776           0 :   if (NS_FAILED(rv)) {
    2777           0 :     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
    2778           0 :     return NS_OK;
    2779             :   }
    2780             : 
    2781             :   ScalarResult sr;
    2782             :   {
    2783           0 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2784           0 :     sr = internal_UpdateKeyedScalar(locker, aName, aKey,
    2785             :                                     ScalarActionType::eSetMaximum,
    2786           0 :                                     unpackedVal);
    2787             :   }
    2788             : 
    2789             :   // Warn the user about the error if we need to.
    2790           0 :   if (sr != ScalarResult::Ok) {
    2791           0 :     internal_LogScalarError(aName, sr);
    2792             :   }
    2793             : 
    2794             :   return NS_OK;
    2795             : }
    2796             : 
    2797             : /**
    2798             :  * Sets the scalar to the maximum of the current and the passed value.
    2799             :  *
    2800             :  * @param aId The scalar enum id.
    2801             :  * @param aValue The numeric value to set the scalar to.
    2802             :  */
    2803             : void
    2804           0 : TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
    2805             : {
    2806           0 :   if (NS_WARN_IF(!IsValidEnumId(aId))) {
    2807           0 :     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    2808             :     return;
    2809             :   }
    2810             : 
    2811           0 :   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
    2812           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2813             : 
    2814           0 :   if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
    2815             :     // We can't record this scalar. Bail out.
    2816           0 :     return;
    2817             :   }
    2818             : 
    2819             :   // Accumulate in the child process if needed.
    2820           0 :   if (!XRE_IsParentProcess()) {
    2821           0 :     TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
    2822             :                                                      ScalarActionType::eSetMaximum,
    2823           0 :                                                      ScalarVariant(aValue));
    2824           0 :     return;
    2825             :   }
    2826             : 
    2827           0 :   if (internal_IsScalarDeserializing(locker)) {
    2828           0 :     internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
    2829             :                                 ScalarActionType::eSetMaximum,
    2830           0 :                                 ScalarVariant(aValue));
    2831           0 :     return;
    2832             :   }
    2833             : 
    2834           0 :   ScalarBase* scalar = nullptr;
    2835             :   nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
    2836           0 :                                          &scalar);
    2837           0 :   if (NS_FAILED(rv)) {
    2838             :     return;
    2839             :   }
    2840             : 
    2841           0 :   scalar->SetMaximum(aValue);
    2842             : }
    2843             : 
    2844             : /**
    2845             :  * Sets the keyed scalar to the maximum of the current and the passed value.
    2846             :  *
    2847             :  * @param aId The scalar enum id.
    2848             :  * @param aKey The key name.
    2849             :  * @param aValue The numeric value to set the scalar to.
    2850             :  */
    2851             : void
    2852           0 : TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
    2853             :                             uint32_t aValue)
    2854             : {
    2855           0 :   if (NS_WARN_IF(!IsValidEnumId(aId))) {
    2856           0 :     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
    2857             :     return;
    2858             :   }
    2859             : 
    2860           0 :   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
    2861           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2862             : 
    2863           0 :   if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
    2864             :     // We can't record this scalar. Bail out.
    2865           0 :     return;
    2866             :   }
    2867             : 
    2868             :   // Accumulate in the child process if needed.
    2869           0 :   if (!XRE_IsParentProcess()) {
    2870           0 :     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
    2871           0 :       aKey, ScalarActionType::eSetMaximum, ScalarVariant(aValue));
    2872           0 :     return;
    2873             :   }
    2874             : 
    2875           0 :   if (internal_IsScalarDeserializing(locker)) {
    2876           0 :     internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
    2877             :                                      aKey,
    2878             :                                      ScalarActionType::eSetMaximum,
    2879           0 :                                      ScalarVariant(aValue));
    2880           0 :     return;
    2881             :   }
    2882             : 
    2883           0 :   KeyedScalar* scalar = nullptr;
    2884             :   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId, ProcessID::Parent,
    2885           0 :                                               &scalar);
    2886           0 :   if (NS_FAILED(rv)) {
    2887             :     return;
    2888             :   }
    2889             : 
    2890           0 :   scalar->SetMaximum(aKey, aValue);
    2891             : }
    2892             : 
    2893             : /**
    2894             :  * Serializes the scalars from the given dataset to a json-style object and resets them.
    2895             :  * The returned structure looks like:
    2896             :  *    {"process": {"category1.probe":1,"category1.other_probe":false,...}, ... }.
    2897             :  *
    2898             :  * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
    2899             :  * @param aClear Whether to clear out the scalars after snapshotting.
    2900             :  */
    2901             : nsresult
    2902           0 : TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx,
    2903             :                                  uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult)
    2904             : {
    2905           0 :   MOZ_ASSERT(XRE_IsParentProcess(),
    2906             :              "Snapshotting scalars should only happen in the parent processes.");
    2907             :   // If no arguments were passed in, apply the default value.
    2908           0 :   if (!optional_argc) {
    2909           0 :     aClearScalars = false;
    2910             :   }
    2911             : 
    2912           0 :   JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
    2913           0 :   if (!root_obj) {
    2914             :     return NS_ERROR_FAILURE;
    2915             :   }
    2916           0 :   aResult.setObject(*root_obj);
    2917             : 
    2918             :   // Return `{}` in child processes.
    2919           0 :   if (!XRE_IsParentProcess()) {
    2920             :     return NS_OK;
    2921             :   }
    2922             : 
    2923             :   // Only lock the mutex while accessing our data, without locking any JS related code.
    2924           0 :   ScalarSnapshotTable scalarsToReflect;
    2925             :   {
    2926           0 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    2927             : 
    2928             :     nsresult rv =
    2929           0 :       internal_GetScalarSnapshot(locker, scalarsToReflect, aDataset, aClearScalars);
    2930           0 :     if (NS_FAILED(rv)) {
    2931           0 :       return rv;
    2932             :     }
    2933             :   }
    2934             : 
    2935             :   // Reflect it to JS.
    2936           0 :   for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
    2937           0 :     ScalarTupleArray& processScalars = iter.Data();
    2938           0 :     const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
    2939             : 
    2940             :     // Create the object that will hold the scalars for this process and add it
    2941             :     // to the returned root object.
    2942           0 :     JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
    2943           0 :     if (!processObj ||
    2944           0 :         !JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
    2945           0 :       return NS_ERROR_FAILURE;
    2946             :     }
    2947             : 
    2948           0 :     for (ScalarTupleArray::size_type i = 0; i < processScalars.Length(); i++) {
    2949           0 :       const ScalarDataTuple& scalar = processScalars[i];
    2950             : 
    2951             :       // Convert it to a JS Val.
    2952           0 :       JS::Rooted<JS::Value> scalarJsValue(aCx);
    2953             :       nsresult rv =
    2954           0 :         nsContentUtils::XPConnect()->VariantToJS(aCx, processObj, mozilla::Get<1>(scalar), &scalarJsValue);
    2955           0 :       if (NS_FAILED(rv)) {
    2956           0 :         return rv;
    2957             :       }
    2958             : 
    2959             :       // Add it to the scalar object.
    2960           0 :       if (!JS_DefineProperty(aCx, processObj, mozilla::Get<0>(scalar), scalarJsValue, JSPROP_ENUMERATE)) {
    2961             :         return NS_ERROR_FAILURE;
    2962             :       }
    2963             :     }
    2964             :   }
    2965             : 
    2966           0 :   return NS_OK;
    2967             : }
    2968             : 
    2969             : /**
    2970             :  * Serializes the scalars from the given dataset to a json-style object and resets them.
    2971             :  * The returned structure looks like:
    2972             :  *   { "process": { "category1.probe": { "key_1": 2, "key_2": 1, ... }, ... }, ... }
    2973             :  *
    2974             :  * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
    2975             :  * @param aClear Whether to clear out the keyed scalars after snapshotting.
    2976             :  */
    2977             : nsresult
    2978           0 : TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx,
    2979             :                                       uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult)
    2980             : {
    2981           0 :   MOZ_ASSERT(XRE_IsParentProcess(),
    2982             :              "Snapshotting scalars should only happen in the parent processes.");
    2983             :   // If no arguments were passed in, apply the default value.
    2984           0 :   if (!optional_argc) {
    2985           0 :     aClearScalars = false;
    2986             :   }
    2987             : 
    2988           0 :   JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
    2989           0 :   if (!root_obj) {
    2990             :     return NS_ERROR_FAILURE;
    2991             :   }
    2992           0 :   aResult.setObject(*root_obj);
    2993             : 
    2994             :   // Return `{}` in child processes.
    2995           0 :   if (!XRE_IsParentProcess()) {
    2996             :     return NS_OK;
    2997             :   }
    2998             : 
    2999             :   // Only lock the mutex while accessing our data, without locking any JS related code.
    3000           0 :   KeyedScalarSnapshotTable scalarsToReflect;
    3001             :   {
    3002           0 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3003             : 
    3004             :     nsresult rv =
    3005           0 :       internal_GetKeyedScalarSnapshot(locker, scalarsToReflect, aDataset, aClearScalars);
    3006           0 :     if (NS_FAILED(rv)) {
    3007           0 :       return rv;
    3008             :     }
    3009             :   }
    3010             : 
    3011             :   // Reflect it to JS.
    3012           0 :   for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
    3013           0 :     KeyedScalarTupleArray& processScalars = iter.Data();
    3014           0 :     const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
    3015             : 
    3016             :     // Create the object that will hold the scalars for this process and add it
    3017             :     // to the returned root object.
    3018           0 :     JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
    3019           0 :     if (!processObj ||
    3020           0 :         !JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
    3021           0 :       return NS_ERROR_FAILURE;
    3022             :     }
    3023             : 
    3024           0 :     for (KeyedScalarTupleArray::size_type i = 0; i < processScalars.Length(); i++) {
    3025           0 :       const KeyedScalarDataTuple& keyedScalarData = processScalars[i];
    3026             : 
    3027             :       // Go through each keyed scalar and create a keyed scalar object.
    3028             :       // This object will hold the values for all the keyed scalar keys.
    3029           0 :       JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx));
    3030             : 
    3031             :       // Define a property for each scalar key, then add it to the keyed scalar
    3032             :       // object.
    3033           0 :       const nsTArray<KeyedScalar::KeyValuePair>& keyProps = mozilla::Get<1>(keyedScalarData);
    3034           0 :       for (uint32_t i = 0; i < keyProps.Length(); i++) {
    3035           0 :         const KeyedScalar::KeyValuePair& keyData = keyProps[i];
    3036             : 
    3037             :         // Convert the value for the key to a JSValue.
    3038           0 :         JS::Rooted<JS::Value> keyJsValue(aCx);
    3039             :         nsresult rv =
    3040           0 :           nsContentUtils::XPConnect()->VariantToJS(aCx, keyedScalarObj, keyData.second(), &keyJsValue);
    3041           0 :         if (NS_FAILED(rv)) {
    3042           0 :           return rv;
    3043             :         }
    3044             : 
    3045             :         // Add the key to the scalar representation.
    3046           0 :         const NS_ConvertUTF8toUTF16 key(keyData.first());
    3047           0 :         if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), keyJsValue, JSPROP_ENUMERATE)) {
    3048           0 :           return NS_ERROR_FAILURE;
    3049             :         }
    3050             :       }
    3051             : 
    3052             :       // Add the scalar to the root object.
    3053           0 :       if (!JS_DefineProperty(aCx, processObj, mozilla::Get<0>(keyedScalarData), keyedScalarObj, JSPROP_ENUMERATE)) {
    3054             :         return NS_ERROR_FAILURE;
    3055             :       }
    3056             :     }
    3057             :   }
    3058             : 
    3059           0 :   return NS_OK;
    3060             : }
    3061             : 
    3062             : nsresult
    3063           0 : TelemetryScalar::RegisterScalars(const nsACString& aCategoryName,
    3064             :                                  JS::Handle<JS::Value> aScalarData,
    3065             :                                  bool aBuiltin,
    3066             :                                  JSContext* cx)
    3067             : {
    3068           0 :   MOZ_ASSERT(XRE_IsParentProcess(),
    3069             :              "Dynamic scalars should only be created in the parent process.");
    3070             : 
    3071           0 :   if (!IsValidIdentifierString(aCategoryName, kMaximumCategoryNameLength, true, false)) {
    3072           0 :     JS_ReportErrorASCII(cx, "Invalid category name %s.",
    3073           0 :                         PromiseFlatCString(aCategoryName).get());
    3074           0 :     return NS_ERROR_INVALID_ARG;
    3075             :   }
    3076             : 
    3077           0 :   if (!aScalarData.isObject()) {
    3078           0 :     JS_ReportErrorASCII(cx, "Scalar data parameter should be an object");
    3079           0 :     return NS_ERROR_INVALID_ARG;
    3080             :   }
    3081             : 
    3082           0 :   JS::RootedObject obj(cx, &aScalarData.toObject());
    3083           0 :   JS::Rooted<JS::IdVector> scalarPropertyIds(cx, JS::IdVector(cx));
    3084           0 :   if (!JS_Enumerate(cx, obj, &scalarPropertyIds)) {
    3085             :     return NS_ERROR_FAILURE;
    3086             :   }
    3087             : 
    3088             :   // Collect the scalar data into local storage first.
    3089             :   // Only after successfully validating all contained scalars will we register
    3090             :   // them into global storage.
    3091           0 :   nsTArray<DynamicScalarInfo> newScalarInfos;
    3092             : 
    3093           0 :   for (size_t i = 0, n = scalarPropertyIds.length(); i < n; i++) {
    3094           0 :     nsAutoJSString scalarName;
    3095           0 :     if (!scalarName.init(cx, scalarPropertyIds[i])) {
    3096           0 :       return NS_ERROR_FAILURE;
    3097             :     }
    3098             : 
    3099           0 :     if (!IsValidIdentifierString(NS_ConvertUTF16toUTF8(scalarName), kMaximumScalarNameLength,
    3100             :                                  false, true)) {
    3101           0 :       JS_ReportErrorASCII(cx, "Invalid scalar name %s.",
    3102           0 :                           PromiseFlatCString(NS_ConvertUTF16toUTF8(scalarName)).get());
    3103           0 :       return NS_ERROR_INVALID_ARG;
    3104             :     }
    3105             : 
    3106             :     // Join the category and the probe names.
    3107             :     nsPrintfCString fullName("%s.%s",
    3108           0 :                              PromiseFlatCString(aCategoryName).get(),
    3109           0 :                              NS_ConvertUTF16toUTF8(scalarName).get());
    3110             : 
    3111           0 :     JS::RootedValue value(cx);
    3112           0 :     if (!JS_GetPropertyById(cx, obj, scalarPropertyIds[i], &value) || !value.isObject()) {
    3113           0 :       return NS_ERROR_FAILURE;
    3114             :     }
    3115           0 :     JS::RootedObject scalarDef(cx, &value.toObject());
    3116             : 
    3117             :     // Get the scalar's kind.
    3118           0 :     if (!JS_GetProperty(cx, scalarDef, "kind", &value)
    3119           0 :       || !value.isInt32()) {
    3120           0 :       JS_ReportErrorASCII(cx, "Invalid or missing 'kind' for scalar %s.",
    3121           0 :                           PromiseFlatCString(fullName).get());
    3122           0 :       return NS_ERROR_FAILURE;
    3123             :     }
    3124           0 :     uint32_t kind = static_cast<uint32_t>(value.toInt32());
    3125             : 
    3126             :     // Get the optional scalar's recording policy (default to false).
    3127           0 :     bool hasProperty = false;
    3128           0 :     bool recordOnRelease = false;
    3129           0 :     if (JS_HasProperty(cx, scalarDef, "record_on_release", &hasProperty) && hasProperty) {
    3130           0 :       if (!JS_GetProperty(cx, scalarDef, "record_on_release", &value) || !value.isBoolean()) {
    3131           0 :         JS_ReportErrorASCII(cx, "Invalid 'record_on_release' for scalar %s.",
    3132           0 :                             PromiseFlatCString(fullName).get());
    3133           0 :         return NS_ERROR_FAILURE;
    3134             :       }
    3135           0 :       recordOnRelease = static_cast<bool>(value.toBoolean());
    3136             :     }
    3137             : 
    3138             :     // Get the optional scalar's keyed (default to false).
    3139           0 :     bool keyed = false;
    3140           0 :     if (JS_HasProperty(cx, scalarDef, "keyed", &hasProperty) && hasProperty) {
    3141           0 :       if (!JS_GetProperty(cx, scalarDef, "keyed", &value) || !value.isBoolean()) {
    3142           0 :         JS_ReportErrorASCII(cx, "Invalid 'keyed' for scalar %s.",
    3143           0 :                             PromiseFlatCString(fullName).get());
    3144           0 :         return NS_ERROR_FAILURE;
    3145             :       }
    3146           0 :       keyed = static_cast<bool>(value.toBoolean());
    3147             :     }
    3148             : 
    3149             : 
    3150             :     // Get the optional scalar's expired state (default to false).
    3151           0 :     bool expired = false;
    3152           0 :     if (JS_HasProperty(cx, scalarDef, "expired", &hasProperty) && hasProperty) {
    3153           0 :       if (!JS_GetProperty(cx, scalarDef, "expired", &value) || !value.isBoolean()) {
    3154           0 :         JS_ReportErrorASCII(cx, "Invalid 'expired' for scalar %s.",
    3155           0 :                             PromiseFlatCString(fullName).get());
    3156           0 :         return NS_ERROR_FAILURE;
    3157             :       }
    3158           0 :       expired = static_cast<bool>(value.toBoolean());
    3159             :     }
    3160             : 
    3161             :     // We defer the actual registration here in case any other event description is invalid.
    3162             :     // In that case we don't need to roll back any partial registration.
    3163           0 :     newScalarInfos.AppendElement(DynamicScalarInfo{
    3164             :       kind, recordOnRelease, expired, fullName, keyed, aBuiltin
    3165           0 :     });
    3166             :   }
    3167             : 
    3168             :   // Register the dynamic definition on the parent process.
    3169             :   {
    3170           0 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3171           0 :     ::internal_RegisterScalars(locker, newScalarInfos);
    3172             : 
    3173             :     // Propagate the registration to all the content-processes. Please note that
    3174             :     // this does not require to hold the mutex.
    3175           0 :     ::internal_BroadcastDefinitions(locker, newScalarInfos);
    3176             :   }
    3177           0 :   return NS_OK;
    3178             : }
    3179             : 
    3180             : /**
    3181             :  * Count in Scalars how many of which events were recorded. See bug 1440673
    3182             :  *
    3183             :  * Event Telemetry unfortunately cannot use vanilla ScalarAdd because it needs
    3184             :  * to summarize events recorded in different processes to the
    3185             :  * telemetry.event_counts of the same process. Including "dynamic".
    3186             :  *
    3187             :  * @param aUniqueEventName - expected to be category#object#method
    3188             :  * @param aProcessType - the process of the event being summarized
    3189             :  * @param aDynamic - whether the event being summarized was dynamic
    3190             :  */
    3191             : void
    3192           0 : TelemetryScalar::SummarizeEvent(const nsCString& aUniqueEventName,
    3193             :                                 ProcessID aProcessType, bool aDynamic)
    3194             : {
    3195           0 :   MOZ_ASSERT(XRE_IsParentProcess(), "Only summarize events in the parent process");
    3196           0 :   if (!XRE_IsParentProcess()) {
    3197           0 :     return;
    3198             :   }
    3199             : 
    3200           0 :   StaticMutexAutoLock lock(gTelemetryScalarsMutex);
    3201             : 
    3202           0 :   ScalarKey scalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_EVENT_COUNTS), aDynamic};
    3203           0 :   if (aDynamic) {
    3204             :     nsresult rv = internal_GetEnumByScalarName(lock,
    3205           0 :                                                nsAutoCString("telemetry.dynamic_event_counts"),
    3206           0 :                                                &scalarKey);
    3207           0 :     if (NS_FAILED(rv)) {
    3208           0 :       NS_WARNING("NS_FAILED getting ScalarKey for telemetry.dynamic_event_counts");
    3209           0 :       return;
    3210             :     }
    3211             :   }
    3212             : 
    3213           0 :   KeyedScalar* scalar = nullptr;
    3214           0 :   nsresult rv = internal_GetKeyedScalarByEnum(lock, scalarKey, aProcessType, &scalar);
    3215             : 
    3216           0 :   if (NS_FAILED(rv)) {
    3217           0 :     NS_WARNING("NS_FAILED getting keyed scalar for event summary. Wut.");
    3218           0 :     return;
    3219             :   }
    3220             : 
    3221             :   static uint32_t sMaxEventSummaryKeys =
    3222           0 :     Preferences::GetUint("toolkit.telemetry.maxEventSummaryKeys", 500);
    3223             : 
    3224             :   // Set this each time as it may have been cleared and recreated between calls
    3225           0 :   scalar->SetMaximumNumberOfKeys(sMaxEventSummaryKeys);
    3226             : 
    3227           0 :   scalar->AddValue(NS_ConvertASCIItoUTF16(aUniqueEventName), 1);
    3228             : }
    3229             : 
    3230             : /**
    3231             :  * Resets all the stored scalars. This is intended to be only used in tests.
    3232             :  */
    3233             : void
    3234           0 : TelemetryScalar::ClearScalars()
    3235             : {
    3236           0 :   MOZ_ASSERT(XRE_IsParentProcess(), "Scalars should only be cleared in the parent process.");
    3237           0 :   if (!XRE_IsParentProcess()) {
    3238           0 :     return;
    3239             :   }
    3240             : 
    3241           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3242           0 :   gScalarStorageMap.Clear();
    3243           0 :   gKeyedScalarStorageMap.Clear();
    3244           0 :   gDynamicBuiltinScalarStorageMap.Clear();
    3245           0 :   gDynamicBuiltinKeyedScalarStorageMap.Clear();
    3246           0 :   gScalarsActions = nullptr;
    3247           0 :   gKeyedScalarsActions = nullptr;
    3248             : }
    3249             : 
    3250             : size_t
    3251           0 : TelemetryScalar::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
    3252             : {
    3253           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3254           0 :   return gScalarNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
    3255             : }
    3256             : 
    3257             : size_t
    3258           0 : TelemetryScalar::GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
    3259             : {
    3260           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3261           0 :   size_t n = 0;
    3262             : 
    3263           0 :   auto getSizeOf = [aMallocSizeOf](auto &storageMap)
    3264           0 :   {
    3265           0 :     size_t partial = 0;
    3266           0 :     for (auto iter = storageMap.Iter(); !iter.Done(); iter.Next()) {
    3267           0 :       auto scalarStorage = iter.UserData();
    3268           0 :       for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
    3269           0 :         auto scalar = childIter.UserData();
    3270           0 :         partial += scalar->SizeOfIncludingThis(aMallocSizeOf);
    3271             :       }
    3272             :     }
    3273           0 :     return partial;
    3274           0 :   };
    3275             : 
    3276             :   // Account for all the storage used for the different scalar types.
    3277           0 :   n += getSizeOf(gScalarStorageMap);
    3278           0 :   n += getSizeOf(gKeyedScalarStorageMap);
    3279           0 :   n += getSizeOf(gDynamicBuiltinScalarStorageMap);
    3280           0 :   n += getSizeOf(gDynamicBuiltinKeyedScalarStorageMap);
    3281             : 
    3282           0 :   return n;
    3283             : }
    3284             : 
    3285             : void
    3286           0 : TelemetryScalar::UpdateChildData(ProcessID aProcessType,
    3287             :                                  const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions)
    3288             : {
    3289           0 :   MOZ_ASSERT(XRE_IsParentProcess(),
    3290             :              "The stored child processes scalar data must be updated from the parent process.");
    3291           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3292             : 
    3293             :   // If scalars are still being deserialized, we need to record the incoming
    3294             :   // operations as well.
    3295           0 :   if (internal_IsScalarDeserializing(locker)) {
    3296           0 :     for (const ScalarAction& action : aScalarActions) {
    3297             :       // We're only getting immutable access, so let's copy it
    3298           0 :       ScalarAction copy = action;
    3299             :       // Fix up the process type
    3300           0 :       copy.mProcessType = aProcessType;
    3301           0 :       internal_RecordScalarAction(locker, copy);
    3302             :     }
    3303             : 
    3304           0 :     return;
    3305             :   }
    3306             : 
    3307           0 :   internal_ApplyScalarActions(locker, aScalarActions, Some(aProcessType));
    3308             : }
    3309             : 
    3310             : void
    3311           0 : TelemetryScalar::UpdateChildKeyedData(ProcessID aProcessType,
    3312             :                                       const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions)
    3313             : {
    3314           0 :   MOZ_ASSERT(XRE_IsParentProcess(),
    3315             :              "The stored child processes keyed scalar data must be updated from the parent process.");
    3316           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3317             : 
    3318             :   // If scalars are still being deserialized, we need to record the incoming
    3319             :   // operations as well.
    3320           0 :   if (internal_IsScalarDeserializing(locker)) {
    3321           0 :     for (const KeyedScalarAction& action : aScalarActions) {
    3322             :       // We're only getting immutable access, so let's copy it
    3323           0 :       KeyedScalarAction copy = action;
    3324             :       // Fix up the process type
    3325           0 :       copy.mProcessType = aProcessType;
    3326           0 :       internal_RecordKeyedScalarAction(locker, copy);
    3327             :     }
    3328             : 
    3329           0 :     return;
    3330             :   }
    3331             : 
    3332           0 :   internal_ApplyKeyedScalarActions(locker, aScalarActions, Some(aProcessType));
    3333             : }
    3334             : 
    3335             : void
    3336           0 : TelemetryScalar::RecordDiscardedData(ProcessID aProcessType,
    3337             :                                      const mozilla::Telemetry::DiscardedData& aDiscardedData)
    3338             : {
    3339           0 :   MOZ_ASSERT(XRE_IsParentProcess(),
    3340             :              "Discarded Data must be updated from the parent process.");
    3341           4 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3342           0 :   if (!internal_CanRecordBase(locker)) {
    3343           0 :     return;
    3344             :   }
    3345             : 
    3346           0 :   ScalarBase* scalar = nullptr;
    3347           4 :   mozilla::DebugOnly<nsresult> rv;
    3348             : 
    3349           0 :   rv = internal_GetScalarByEnum(locker,
    3350           0 :     ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_ACCUMULATIONS), false},
    3351           0 :     aProcessType, &scalar);
    3352           0 :   MOZ_ASSERT(NS_SUCCEEDED(rv));
    3353           2 :   scalar->AddValue(aDiscardedData.mDiscardedHistogramAccumulations);
    3354             : 
    3355           2 :   rv = internal_GetScalarByEnum(locker,
    3356           4 :     ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_KEYED_ACCUMULATIONS), false},
    3357           4 :     aProcessType, &scalar);
    3358           2 :   MOZ_ASSERT(NS_SUCCEEDED(rv));
    3359           2 :   scalar->AddValue(aDiscardedData.mDiscardedKeyedHistogramAccumulations);
    3360             : 
    3361           2 :   rv = internal_GetScalarByEnum(locker,
    3362           4 :     ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_SCALAR_ACTIONS), false},
    3363           0 :     aProcessType, &scalar);
    3364           0 :   MOZ_ASSERT(NS_SUCCEEDED(rv));
    3365           2 :   scalar->AddValue(aDiscardedData.mDiscardedScalarActions);
    3366             : 
    3367           2 :   rv = internal_GetScalarByEnum(locker,
    3368           4 :     ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_KEYED_SCALAR_ACTIONS), false},
    3369           0 :     aProcessType, &scalar);
    3370           0 :   MOZ_ASSERT(NS_SUCCEEDED(rv));
    3371           2 :   scalar->AddValue(aDiscardedData.mDiscardedKeyedScalarActions);
    3372             : 
    3373           2 :   rv = internal_GetScalarByEnum(locker,
    3374           4 :     ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_CHILD_EVENTS), false},
    3375           4 :     aProcessType, &scalar);
    3376           2 :   MOZ_ASSERT(NS_SUCCEEDED(rv));
    3377           2 :   scalar->AddValue(aDiscardedData.mDiscardedChildEvents);
    3378             : }
    3379             : 
    3380             : /**
    3381             :  * Get the dynamic scalar definitions in an IPC-friendly
    3382             :  * structure.
    3383             :  */
    3384             : void
    3385           1 : TelemetryScalar::GetDynamicScalarDefinitions(
    3386             :   nsTArray<DynamicScalarDefinition> &aDefArray)
    3387             : {
    3388           1 :   MOZ_ASSERT(XRE_IsParentProcess());
    3389           0 :   if (!gDynamicScalarInfo) {
    3390             :     // Don't have dynamic scalar definitions. Bail out!
    3391           0 :     return;
    3392             :   }
    3393             : 
    3394           0 :   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3395           1 :   internal_DynamicScalarToIPC(locker, *gDynamicScalarInfo, aDefArray);
    3396             : }
    3397             : 
    3398             : /**
    3399             :  * This adds the dynamic scalar definitions coming from
    3400             :  * the parent process to this child process. If a dynamic
    3401             :  * scalar definition is already defined, check if the new definition
    3402             :  * makes the scalar expired and eventually update the expiration
    3403             :  * state.
    3404             :  */
    3405             : void
    3406           0 : TelemetryScalar::AddDynamicScalarDefinitions(
    3407             :   const nsTArray<DynamicScalarDefinition>& aDefs)
    3408             : {
    3409           0 :   MOZ_ASSERT(!XRE_IsParentProcess());
    3410             : 
    3411           0 :   nsTArray<DynamicScalarInfo> dynamicStubs;
    3412             : 
    3413             :   // Populate the definitions array before acquiring the lock.
    3414           0 :   for (auto def : aDefs) {
    3415           0 :     bool recordOnRelease = def.dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT;
    3416           0 :     dynamicStubs.AppendElement(DynamicScalarInfo{
    3417             :       def.type,
    3418             :       recordOnRelease,
    3419           0 :       def.expired,
    3420             :       def.name,
    3421           0 :       def.keyed,
    3422           0 :       false /* builtin */});
    3423             :   }
    3424             : 
    3425             :   {
    3426           0 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3427           0 :     internal_RegisterScalars(locker, dynamicStubs);
    3428             :   }
    3429           0 : }
    3430             : 
    3431             : ////////////////////////////////////////////////////////////////////////
    3432             : ////////////////////////////////////////////////////////////////////////
    3433             : //
    3434             : // PUBLIC: GeckoView serialization/deserialization functions.
    3435             : 
    3436             : /**
    3437             :  * Write the scalar data to the provided Json object, for
    3438             :  * GeckoView measurement persistence. The output format is the same one used
    3439             :  * for snapshotting the scalars.
    3440             :  *
    3441             :  * @param {aWriter} The JSON object to write to.
    3442             :  * @returns NS_OK or a failure value explaining why persistence failed.
    3443             :  */
    3444             : nsresult
    3445           0 : TelemetryScalar::SerializeScalars(mozilla::JSONWriter& aWriter)
    3446             : {
    3447             :   // Get a copy of the data, without clearing.
    3448           0 :   ScalarSnapshotTable scalarsToReflect;
    3449             :   {
    3450           0 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3451             :     // For persistence, we care about all the datasets. Worst case, they
    3452             :     // will be empty.
    3453             :     nsresult rv = internal_GetScalarSnapshot(locker,
    3454             :                                              scalarsToReflect,
    3455             :                                              nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
    3456           0 :                                              false /*aClearScalars*/);
    3457           0 :     if (NS_FAILED(rv)) {
    3458           0 :       return rv;
    3459             :     }
    3460             :   }
    3461             : 
    3462             :   // Persist the scalars to the JSON object.
    3463           0 :   for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
    3464           0 :     ScalarTupleArray& processScalars = iter.Data();
    3465           0 :     const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
    3466             : 
    3467           0 :     aWriter.StartObjectProperty(processName);
    3468             : 
    3469           0 :     for (const ScalarDataTuple& scalar : processScalars) {
    3470           0 :       nsresult rv = WriteVariantToJSONWriter(mozilla::Get<2>(scalar) /*aScalarType*/,
    3471           0 :                                              mozilla::Get<1>(scalar) /*aInputValue*/,
    3472           0 :                                              mozilla::Get<0>(scalar) /*aPropertyName*/,
    3473           0 :                                              aWriter /*aWriter*/);
    3474           0 :       if (NS_FAILED(rv)) {
    3475             :         // Skip this scalar if we failed to write it. We don't bail out just
    3476             :         // yet as we may salvage other scalars. We eventually need to call EndObject.
    3477             :         continue;
    3478             :       }
    3479             :     }
    3480             : 
    3481           0 :     aWriter.EndObject();
    3482             :   }
    3483             : 
    3484           0 :   return NS_OK;
    3485             : }
    3486             : 
    3487             : /**
    3488             :  * Write the keyed scalar data to the provided Json object, for
    3489             :  * GeckoView measurement persistence. The output format is the same
    3490             :  * one used for snapshotting the keyed scalars.
    3491             :  *
    3492             :  * @param {aWriter} The JSON object to write to.
    3493             :  * @returns NS_OK or a failure value explaining why persistence failed.
    3494             :  */
    3495             : nsresult
    3496           0 : TelemetryScalar::SerializeKeyedScalars(mozilla::JSONWriter& aWriter)
    3497             : {
    3498             :   // Get a copy of the data, without clearing.
    3499           0 :   KeyedScalarSnapshotTable keyedScalarsToReflect;
    3500             :   {
    3501           0 :     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
    3502             :     // For persistence, we care about all the datasets. Worst case, they
    3503             :     // will be empty.
    3504             :     nsresult rv = internal_GetKeyedScalarSnapshot(locker,
    3505             :                                          keyedScalarsToReflect,
    3506             :                                          nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
    3507           0 :                                          false /*aClearScalars*/);
    3508           0 :     if (NS_FAILED(rv)) {
    3509           0 :       return rv;
    3510             :     }
    3511             :   }
    3512             : 
    3513             :   // Persist the scalars to the JSON object.
    3514           0 :   for (auto iter = keyedScalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
    3515           0 :     KeyedScalarTupleArray& processScalars = iter.Data();
    3516           0 :     const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
    3517             : 
    3518           0 :     aWriter.StartObjectProperty(processName);
    3519             : 
    3520           0 :     for (const KeyedScalarDataTuple& keyedScalarData : processScalars) {
    3521           0 :       aWriter.StartObjectProperty(mozilla::Get<0>(keyedScalarData));
    3522             : 
    3523             :       // Define a property for each scalar key, then add it to the keyed scalar
    3524             :       // object.
    3525           0 :       const nsTArray<KeyedScalar::KeyValuePair>& keyProps = mozilla::Get<1>(keyedScalarData);
    3526           0 :       for (const KeyedScalar::KeyValuePair& keyData : keyProps) {
    3527           0 :         nsresult rv = WriteVariantToJSONWriter(mozilla::Get<2>(keyedScalarData) /*aScalarType*/,
    3528           0 :                                                keyData.second() /*aInputValue*/,
    3529           0 :                                                PromiseFlatCString(keyData.first()).get() /*aOutKey*/,
    3530           0 :                                                aWriter /*aWriter*/);
    3531           0 :         if (NS_FAILED(rv)) {
    3532             :           // Skip this scalar if we failed to write it. We don't bail out just
    3533             :           // yet as we may salvage other scalars. We eventually need to call EndObject.
    3534             :           continue;
    3535             :         }
    3536             :       }
    3537           0 :       aWriter.EndObject();
    3538             :     }
    3539           0 :     aWriter.EndObject();
    3540             :   }
    3541             : 
    3542           0 :   return NS_OK;
    3543             : }
    3544             : 
    3545             : /**
    3546             :  * Load the persisted measurements from a Json object and inject them
    3547             :  * in the relevant process storage.
    3548             :  *
    3549             :  * @param {aData} The input Json object.
    3550             :  * @returns NS_OK if loading was performed, an error code explaining the
    3551             :  *          failure reason otherwise.
    3552             :  */
    3553             : nsresult
    3554           0 : TelemetryScalar::DeserializePersistedScalars(JSContext* aCx, JS::HandleValue aData)
    3555             : {
    3556           0 :   MOZ_ASSERT(XRE_IsParentProcess(), "Only load scalars in the parent process");
    3557           0 :   if (!XRE_IsParentProcess()) {
    3558             :     return NS_ERROR_FAILURE;
    3559             :   }
    3560             : 
    3561             :   typedef mozilla::Pair<nsCString, nsCOMPtr<nsIVariant>> PersistedScalarPair;
    3562             :   typedef nsTArray<PersistedScalarPair> PersistedScalarArray;
    3563             :   typedef nsDataHashtable<ProcessIDHashKey, PersistedScalarArray> PeristedScalarStorage;
    3564             : 
    3565           0 :   PeristedScalarStorage scalarsToUpdate;
    3566             : 
    3567             :   // Before updating the scalars, we need to get the data out of the JS
    3568             :   // wrappers. We can't hold the scalars mutex while handling JS stuff.
    3569             :   // Build a <scalar name, value> map.
    3570           0 :   JS::RootedObject scalarDataObj(aCx, &aData.toObject());
    3571           0 :   JS::Rooted<JS::IdVector> processes(aCx, JS::IdVector(aCx));
    3572           0 :   if (!JS_Enumerate(aCx, scalarDataObj, &processes)) {
    3573             :     // We can't even enumerate the processes in the loaded data, so
    3574             :     // there is nothing we could recover from the persistence file. Bail out.
    3575           0 :     JS_ClearPendingException(aCx);
    3576           0 :     return NS_ERROR_FAILURE;
    3577             :   }
    3578             : 
    3579             :   // The following block of code attempts to extract as much data as possible
    3580             :   // from the serialized JSON, even in case of light data corruptions: if, for example,
    3581             :   // the data for a single process is corrupted or is in an unexpected form, we press on
    3582             :   // and attempt to load the data for the other processes.
    3583           0 :   JS::RootedId process(aCx);
    3584           0 :   for (auto& processVal : processes) {
    3585             :     // This is required as JS API calls require an Handle<jsid> and not a
    3586             :     // plain jsid.
    3587           0 :     process = processVal;
    3588             :     // Get the process name.
    3589           0 :     nsAutoJSString processNameJS;
    3590           0 :     if (!processNameJS.init(aCx, process)) {
    3591           0 :       JS_ClearPendingException(aCx);
    3592           0 :       continue;
    3593             :     }
    3594             : 
    3595             :     // Make sure it's valid. Note that this is safe to call outside
    3596             :     // of a locked section.
    3597           0 :     NS_ConvertUTF16toUTF8 processName(processNameJS);
    3598           0 :     ProcessID processID = GetIDForProcessName(processName.get());
    3599           0 :     if (processID == ProcessID::Count) {
    3600           0 :       NS_WARNING(nsPrintfCString("Failed to get process ID for %s", processName.get()).get());
    3601           0 :       continue;
    3602             :     }
    3603             : 
    3604             :     // And its probes.
    3605           0 :     JS::RootedValue processData(aCx);
    3606           0 :     if (!JS_GetPropertyById(aCx, scalarDataObj, process, &processData)) {
    3607           0 :       JS_ClearPendingException(aCx);
    3608           0 :       continue;
    3609             :     }
    3610             : 
    3611           0 :     if (!processData.isObject()) {
    3612             :       // |processData| should be an object containing scalars. If this is
    3613             :       // not the case, silently skip and try to load the data for the other
    3614             :       // processes.
    3615             :       continue;
    3616             :     }
    3617             : 
    3618             :     // Iterate through each scalar.
    3619           0 :     JS::RootedObject processDataObj(aCx, &processData.toObject());
    3620           0 :     JS::Rooted<JS::IdVector> scalars(aCx, JS::IdVector(aCx));
    3621           0 :     if (!JS_Enumerate(aCx, processDataObj, &scalars)) {
    3622           0 :       JS_ClearPendingException(aCx);
    3623           0 :       continue;
    3624             :     }
    3625             : 
    3626           0 :     JS::RootedId scalar(aCx);
    3627           0 :     for (auto& scalarVal : scalars) {
    3628           0 :       scalar = scalarVal;
    3629             :       // Get the scalar name.
    3630           0 :       nsAutoJSString scalarName;
    3631           0 :       if (!scalarName.init(aCx, scalar)) {
    3632           0 :         JS_ClearPendingException(aCx);
    3633           0 :         continue;
    3634             :       }
    3635             : 
    3636             :       // Get the scalar value as a JS value.
    3637           0 :       JS::RootedValue scalarValue(aCx);
    3638           0 :       if (!JS_GetPropertyById(aCx, processDataObj, scalar, &scalarValue)) {
    3639           0 :         JS_ClearPendingException(aCx);
    3640           0 :         continue;
    3641             :       }
    3642             : 
    3643           0 :       if (scalarValue.isNullOrUndefined()) {
    3644             :         // We can't set scalars to null or undefined values, skip this
    3645             :         // and try to load other scalars.
    3646             :         continue;
    3647             :       }
    3648             : 
    3649             :       // Unpack the aVal to nsIVariant.
    3650           0 :       nsCOMPtr<nsIVariant> unpackedVal;
    3651             :       nsresult rv =
    3652           0 :         nsContentUtils::XPConnect()->JSToVariant(aCx, scalarValue,  getter_AddRefs(unpackedVal));
    3653           0 :       if (NS_FAILED(rv)) {
    3654           0 :         JS_ClearPendingException(aCx);
    3655           0 :         continue;
    3656             :       }
    3657             : 
    3658             :       // Add the scalar to the map.
    3659             :       PersistedScalarArray& processScalars =
    3660           0 :         scalarsToUpdate.GetOrInsert(static_cast<uint32_t>(processID));
    3661           0 :       processScalars.AppendElement(
    3662           0 :         mozilla::MakePair(nsCString(NS_ConvertUTF16toUTF8(scalarName)), unpackedVal));
    3663             :     }
    3664             :   }
    3665             : 
    3666             :   // Now that all the JS specific operations are finished, update the scalars.
    3667             :   {
    3668           0 :     StaticMutexAutoLock lock(gTelemetryScalarsMutex);
    3669             : 
    3670           0 :     for (auto iter = scalarsToUpdate.ConstIter(); !iter.Done(); iter.Next()) {
    3671           0 :       PersistedScalarArray& processScalars = iter.Data();
    3672           0 :       for (PersistedScalarArray::size_type i = 0; i < processScalars.Length(); i++) {
    3673           0 :         mozilla::Unused << internal_UpdateScalar(lock,
    3674           0 :                                                  processScalars[i].first(),
    3675             :                                                  ScalarActionType::eSet,
    3676           0 :                                                  processScalars[i].second(),
    3677           0 :                                                  ProcessID(iter.Key()),
    3678             :                                                  true /* aForce */);
    3679             :       }
    3680             :     }
    3681             :   }
    3682             : 
    3683             :   return NS_OK;
    3684             : }
    3685             : 
    3686             : /**
    3687             :  * Load the persisted measurements from a Json object and injects them
    3688             :  * in the relevant process storage.
    3689             :  *
    3690             :  * @param {aData} The input Json object.
    3691             :  * @returns NS_OK if loading was performed, an error code explaining the
    3692             :  *          failure reason otherwise.
    3693             :  */
    3694             : nsresult
    3695           0 : TelemetryScalar::DeserializePersistedKeyedScalars(JSContext* aCx, JS::HandleValue aData)
    3696             : {
    3697           0 :   MOZ_ASSERT(XRE_IsParentProcess(), "Only load scalars in the parent process");
    3698           0 :   if (!XRE_IsParentProcess()) {
    3699             :     return NS_ERROR_FAILURE;
    3700             :   }
    3701             : 
    3702             :   typedef mozilla::Tuple<nsCString, nsString, nsCOMPtr<nsIVariant>> PersistedKeyedScalarTuple;
    3703             :   typedef nsTArray<PersistedKeyedScalarTuple> PersistedKeyedScalarArray;
    3704             :   typedef nsDataHashtable<ProcessIDHashKey, PersistedKeyedScalarArray> PeristedKeyedScalarStorage;
    3705             : 
    3706           0 :   PeristedKeyedScalarStorage scalarsToUpdate;
    3707             : 
    3708             :   // Before updating the keyed scalars, we need to get the data out of the JS
    3709             :   // wrappers. We can't hold the scalars mutex while handling JS stuff.
    3710             :   // Build a <scalar name, value> map.
    3711           0 :   JS::RootedObject scalarDataObj(aCx, &aData.toObject());
    3712           0 :   JS::Rooted<JS::IdVector> processes(aCx, JS::IdVector(aCx));
    3713           0 :   if (!JS_Enumerate(aCx, scalarDataObj, &processes)) {
    3714             :     // We can't even enumerate the processes in the loaded data, so
    3715             :     // there is nothing we could recover from the persistence file. Bail out.
    3716           0 :     JS_ClearPendingException(aCx);
    3717           0 :     return NS_ERROR_FAILURE;
    3718             :   }
    3719             : 
    3720             :   // The following block of code attempts to extract as much data as possible
    3721             :   // from the serialized JSON, even in case of light data corruptions: if, for example,
    3722             :   // the data for a single process is corrupted or is in an unexpected form, we press on
    3723             :   // and attempt to load the data for the other processes.
    3724           0 :   JS::RootedId process(aCx);
    3725           0 :   for (auto& processVal : processes) {
    3726           0 :     process = processVal;
    3727             :     // Get the process name.
    3728           0 :     nsAutoJSString processNameJS;
    3729           0 :     if (!processNameJS.init(aCx, process)) {
    3730           0 :       JS_ClearPendingException(aCx);
    3731           0 :       continue;
    3732             :     }
    3733             : 
    3734             :     // Make sure it's valid. Note that this is safe to call outside
    3735             :     // of a locked section.
    3736           0 :     NS_ConvertUTF16toUTF8 processName(processNameJS);
    3737           0 :     ProcessID processID = GetIDForProcessName(processName.get());
    3738           0 :     if (processID == ProcessID::Count) {
    3739           0 :       NS_WARNING(nsPrintfCString("Failed to get process ID for %s", processName.get()).get());
    3740           0 :       continue;
    3741             :     }
    3742             : 
    3743             :     // And its probes.
    3744           0 :     JS::RootedValue processData(aCx);
    3745           0 :     if (!JS_GetPropertyById(aCx, scalarDataObj, process, &processData)) {
    3746           0 :       JS_ClearPendingException(aCx);
    3747           0 :       continue;
    3748             :     }
    3749             : 
    3750           0 :     if (!processData.isObject()) {
    3751             :       // |processData| should be an object containing scalars. If this is
    3752             :       // not the case, silently skip and try to load the data for the other
    3753             :       // processes.
    3754             :       continue;
    3755             :     }
    3756             : 
    3757             :     // Iterate through each keyed scalar.
    3758           0 :     JS::RootedObject processDataObj(aCx, &processData.toObject());
    3759           0 :     JS::Rooted<JS::IdVector> keyedScalars(aCx, JS::IdVector(aCx));
    3760           0 :     if (!JS_Enumerate(aCx, processDataObj, &keyedScalars)) {
    3761           0 :       JS_ClearPendingException(aCx);
    3762           0 :       continue;
    3763             :     }
    3764             : 
    3765           0 :     JS::RootedId keyedScalar(aCx);
    3766           0 :     for (auto& keyedScalarVal : keyedScalars) {
    3767           0 :       keyedScalar = keyedScalarVal;
    3768             :       // Get the scalar name.
    3769           0 :       nsAutoJSString scalarName;
    3770           0 :       if (!scalarName.init(aCx, keyedScalar)) {
    3771           0 :         JS_ClearPendingException(aCx);
    3772           0 :         continue;
    3773             :       }
    3774             : 
    3775             :       // Get the data for this keyed scalar.
    3776           0 :       JS::RootedValue keyedScalarData(aCx);
    3777           0 :       if (!JS_GetPropertyById(aCx, processDataObj, keyedScalar, &keyedScalarData)) {
    3778           0 :         JS_ClearPendingException(aCx);
    3779           0 :         continue;
    3780             :       }
    3781             : 
    3782           0 :       if (!keyedScalarData.isObject()) {
    3783             :         // Keyed scalar data need to be an object. If that's not the case, skip it
    3784             :         // and try to load the rest of the data.
    3785             :         continue;
    3786             :       }
    3787             : 
    3788             :       // Get the keys in the keyed scalar.
    3789           0 :       JS::RootedObject keyedScalarDataObj(aCx, &keyedScalarData.toObject());
    3790           0 :       JS::Rooted<JS::IdVector> keys(aCx, JS::IdVector(aCx));
    3791           0 :       if (!JS_Enumerate(aCx, keyedScalarDataObj, &keys)) {
    3792           0 :         JS_ClearPendingException(aCx);
    3793           0 :         continue;
    3794             :       }
    3795             : 
    3796           0 :       JS::RootedId key(aCx);
    3797           0 :       for (auto keyVal : keys) {
    3798           0 :         key = keyVal;
    3799             :         // Get the process name.
    3800           0 :         nsAutoJSString keyName;
    3801           0 :         if (!keyName.init(aCx, key)) {
    3802           0 :           JS_ClearPendingException(aCx);
    3803           0 :           continue;
    3804             :         }
    3805             : 
    3806             :         // Get the scalar value as a JS value.
    3807           0 :         JS::RootedValue scalarValue(aCx);
    3808           0 :         if (!JS_GetPropertyById(aCx, keyedScalarDataObj, key, &scalarValue)) {
    3809           0 :           JS_ClearPendingException(aCx);
    3810           0 :           continue;
    3811             :         }
    3812             : 
    3813           0 :         if (scalarValue.isNullOrUndefined()) {
    3814             :           // We can't set scalars to null or undefined values, skip this
    3815             :           // and try to load other scalars.
    3816             :           continue;
    3817             :         }
    3818             : 
    3819             :         // Unpack the aVal to nsIVariant.
    3820           0 :         nsCOMPtr<nsIVariant> unpackedVal;
    3821             :         nsresult rv =
    3822           0 :           nsContentUtils::XPConnect()->JSToVariant(aCx, scalarValue,  getter_AddRefs(unpackedVal));
    3823           0 :         if (NS_FAILED(rv)) {
    3824           0 :           JS_ClearPendingException(aCx);
    3825           0 :           continue;
    3826             :         }
    3827             : 
    3828             :         // Add the scalar to the map.
    3829             :         PersistedKeyedScalarArray& processScalars =
    3830           0 :           scalarsToUpdate.GetOrInsert(static_cast<uint32_t>(processID));
    3831           0 :         processScalars.AppendElement(
    3832           0 :           mozilla::MakeTuple(nsCString(NS_ConvertUTF16toUTF8(scalarName)),
    3833             :                              nsString(keyName), unpackedVal));
    3834             :       }
    3835             :     }
    3836             :   }
    3837             : 
    3838             :   // Now that all the JS specific operations are finished, update the scalars.
    3839             :   {
    3840             :     StaticMutexAutoLock lock(gTelemetryScalarsMutex);
    3841             : 
    3842             :     for (auto iter = scalarsToUpdate.ConstIter(); !iter.Done(); iter.Next()) {
    3843             :       PersistedKeyedScalarArray& processScalars = iter.Data();
    3844             :       for (PersistedKeyedScalarArray::size_type i = 0; i < processScalars.Length(); i++) {
    3845             :         mozilla::Unused << internal_UpdateKeyedScalar(lock,
    3846             :                                                       mozilla::Get<0>(processScalars[i]),
    3847             :                                                       mozilla::Get<1>(processScalars[i]),
    3848             :                                                       ScalarActionType::eSet,
    3849             :                                                       mozilla::Get<2>(processScalars[i]),
    3850             :                                                       ProcessID(iter.Key()),
    3851             :                                                       true /* aForce */);
    3852             :       }
    3853             :     }
    3854             :   }
    3855             : 
    3856             :   return NS_OK;
    3857             : }

Generated by: LCOV version 1.13-14-ga5dd952