Bug 1508310 - Implement Report-to header support - part 5 - Report delivering, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Sat, 01 Dec 2018 21:26:09 +0100
changeset 505579 a00225dfc4146cbe04d03c28de49d4fed41cf04c
parent 505578 ad2c155ce3518b4304161897a850620aa8912291
child 505580 f2492c85277752cf0e0aa6a897e8ac975522f04c
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1508310
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1508310 - Implement Report-to header support - part 5 - Report delivering, r=smaug
dom/fetch/InternalRequest.h
dom/reporting/DeprecationReportBody.cpp
dom/reporting/DeprecationReportBody.h
dom/reporting/FeaturePolicyViolationReportBody.cpp
dom/reporting/FeaturePolicyViolationReportBody.h
dom/reporting/ReportBody.cpp
dom/reporting/ReportBody.h
dom/reporting/ReportDeliver.cpp
dom/reporting/ReportDeliver.h
modules/libpref/init/StaticPrefList.h
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -320,16 +320,21 @@ class InternalRequest final {
   }
 
   bool UnsafeRequest() const { return mUnsafeRequest; }
 
   void SetUnsafeRequest() { mUnsafeRequest = true; }
 
   InternalHeaders* Headers() { return mHeaders; }
 
+  void SetHeaders(InternalHeaders* aHeaders) {
+    MOZ_ASSERT(aHeaders);
+    mHeaders = aHeaders;
+  }
+
   bool SameOriginDataURL() const { return mSameOriginDataURL; }
 
   void UnsetSameOriginDataURL() { mSameOriginDataURL = false; }
 
   void SetBody(nsIInputStream* aStream, int64_t aBodyLength) {
     // A request's body may not be reset once set.
     MOZ_ASSERT_IF(aStream, !mBodyStream);
     mBodyStream = aStream;
--- a/dom/reporting/DeprecationReportBody.cpp
+++ b/dom/reporting/DeprecationReportBody.cpp
@@ -50,17 +50,17 @@ void DeprecationReportBody::GetSourceFil
 Nullable<uint32_t> DeprecationReportBody::GetLineNumber() const {
   return mLineNumber;
 }
 
 Nullable<uint32_t> DeprecationReportBody::GetColumnNumber() const {
   return mColumnNumber;
 }
 
-void DeprecationReportBody::ToJSONInternal(JSONWriter& aWriter) const {
+void DeprecationReportBody::ToJSON(JSONWriter& aWriter) const {
   aWriter.StringProperty("id", NS_ConvertUTF16toUTF8(mId).get());
   // TODO: anticipatedRemoval? https://github.com/w3c/reporting/issues/132
   aWriter.StringProperty("message", NS_ConvertUTF16toUTF8(mMessage).get());
 
   if (mSourceFile.IsEmpty()) {
     aWriter.NullProperty("sourceFile");
   } else {
     aWriter.StringProperty("sourceFile",
--- a/dom/reporting/DeprecationReportBody.h
+++ b/dom/reporting/DeprecationReportBody.h
@@ -32,17 +32,17 @@ class DeprecationReportBody final : publ
 
   void GetSourceFile(nsAString& aSourceFile) const;
 
   Nullable<uint32_t> GetLineNumber() const;
 
   Nullable<uint32_t> GetColumnNumber() const;
 
  protected:
-  void ToJSONInternal(JSONWriter& aJSONWriter) const override;
+  void ToJSON(JSONWriter& aJSONWriter) const override;
 
  private:
   ~DeprecationReportBody();
 
   const nsString mId;
   const Nullable<Date> mDate;
   const nsString mMessage;
   const nsString mSourceFile;
--- a/dom/reporting/FeaturePolicyViolationReportBody.cpp
+++ b/dom/reporting/FeaturePolicyViolationReportBody.cpp
@@ -46,18 +46,17 @@ Nullable<int32_t> FeaturePolicyViolation
   return mColumnNumber;
 }
 
 void FeaturePolicyViolationReportBody::GetDisposition(
     nsAString& aDisposition) const {
   aDisposition = mDisposition;
 }
 
-void FeaturePolicyViolationReportBody::ToJSONInternal(
-    JSONWriter& aWriter) const {
+void FeaturePolicyViolationReportBody::ToJSON(JSONWriter& aWriter) const {
   aWriter.StringProperty("featureId", NS_ConvertUTF16toUTF8(mFeatureId).get());
 
   if (mSourceFile.IsEmpty()) {
     aWriter.NullProperty("sourceFile");
   } else {
     aWriter.StringProperty("sourceFile",
                            NS_ConvertUTF16toUTF8(mSourceFile).get());
   }
--- a/dom/reporting/FeaturePolicyViolationReportBody.h
+++ b/dom/reporting/FeaturePolicyViolationReportBody.h
@@ -31,17 +31,17 @@ class FeaturePolicyViolationReportBody f
 
   Nullable<int32_t> GetLineNumber() const;
 
   Nullable<int32_t> GetColumnNumber() const;
 
   void GetDisposition(nsAString& aDisposition) const;
 
  protected:
-  void ToJSONInternal(JSONWriter& aJSONWriter) const override;
+  void ToJSON(JSONWriter& aJSONWriter) const override;
 
  private:
   ~FeaturePolicyViolationReportBody();
 
   const nsString mFeatureId;
   const nsString mSourceFile;
   const Nullable<int32_t> mLineNumber;
   const Nullable<int32_t> mColumnNumber;
--- a/dom/reporting/ReportBody.cpp
+++ b/dom/reporting/ReportBody.cpp
@@ -26,31 +26,10 @@ ReportBody::ReportBody(nsPIDOMWindowInne
 
 ReportBody::~ReportBody() = default;
 
 JSObject* ReportBody::WrapObject(JSContext* aCx,
                                  JS::Handle<JSObject*> aGivenProto) {
   return ReportBody_Binding::Wrap(aCx, this, aGivenProto);
 }
 
-namespace {
-
-struct StringWriteFunc : public JSONWriteFunc {
-  nsAString& mBuffer;  // The lifetime of the struct must be bound to the buffer
-  explicit StringWriteFunc(nsAString& aBuffer) : mBuffer(aBuffer) {}
-
-  void Write(const char* aStr) override {
-    mBuffer.Append(NS_ConvertUTF8toUTF16(aStr));
-  }
-};
-
-}  // namespace
-
-void ReportBody::ToJSON(nsAString& aJSON) const {
-  JSONWriter w(MakeUnique<StringWriteFunc>(aJSON));
-
-  w.Start();
-  ToJSONInternal(w);
-  w.End();
-}
-
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/reporting/ReportBody.h
+++ b/dom/reporting/ReportBody.h
@@ -27,22 +27,20 @@ class ReportBody : public nsISupports, p
 
   explicit ReportBody(nsPIDOMWindowInner* aWindow);
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
 
-  void ToJSON(nsAString& aJSON) const;
+  virtual void ToJSON(JSONWriter& aJSONWriter) const = 0;
 
  protected:
   virtual ~ReportBody();
 
-  virtual void ToJSONInternal(JSONWriter& aJSONWriter) const = 0;
-
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_ReportBody_h
--- a/dom/reporting/ReportDeliver.cpp
+++ b/dom/reporting/ReportDeliver.cpp
@@ -1,35 +1,267 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/EndpointForReportChild.h"
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ReportDeliver.h"
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/Response.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "nsGlobalWindowInner.h"
+#include "nsIGlobalObject.h"
+#include "nsNetUtil.h"
+#include "nsStringStream.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace {
+
+StaticRefPtr<ReportDeliver> gReportDeliver;
+
+class ReportFetchHandler final : public PromiseNativeHandler {
+ public:
+  NS_DECL_ISUPPORTS
+
+  explicit ReportFetchHandler(const ReportDeliver::ReportData& aReportData)
+      : mReportData(aReportData) {}
+
+  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
+    if (!gReportDeliver) {
+      return;
+    }
+
+    if (NS_WARN_IF(!aValue.isObject())) {
+      return;
+    }
+
+    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+    MOZ_ASSERT(obj);
+
+    {
+      Response* response = nullptr;
+      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Response, &obj, response)))) {
+        return;
+      }
+
+      if (response->Status() == 410) {
+        // TODO: remove
+        return;
+      }
+    }
+  }
+
+  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
+    if (gReportDeliver) {
+      ++mReportData.mFailures;
+      gReportDeliver->AppendReportData(mReportData);
+    }
+  }
+
+ private:
+  ~ReportFetchHandler() = default;
+
+  ReportDeliver::ReportData mReportData;
+};
+
+NS_IMPL_ISUPPORTS0(ReportFetchHandler)
+
+// This RAII class keeps a list of sandboxed globals for the delivering of
+// reports. In this way, if we have to deliver more than 1 report to the same
+// origin, we reuse the sandbox.
+class MOZ_RAII SandboxGlobalHolder final {
+ public:
+  nsIGlobalObject* GetOrCreateSandboxGlobalObject(nsIPrincipal* aPrincipal) {
+    MOZ_ASSERT(aPrincipal);
+
+    nsAutoCString origin;
+    nsresult rv = aPrincipal->GetOrigin(origin);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
+
+    nsCOMPtr<nsIGlobalObject> globalObject = mGlobals.Get(origin);
+    if (globalObject) {
+      return globalObject;
+    }
+
+    nsIXPConnect* xpc = nsContentUtils::XPConnect();
+    MOZ_ASSERT(xpc, "This should never be null!");
+
+    AutoJSAPI jsapi;
+    jsapi.Init();
+
+    JSContext* cx = jsapi.cx();
+    JS::Rooted<JSObject*> sandbox(cx);
+    rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
+
+    // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
+    // global.
+    MOZ_ASSERT(JS_IsGlobalObject(sandbox));
+
+    globalObject = xpc::NativeGlobal(sandbox);
+    if (NS_WARN_IF(!globalObject)) {
+      return nullptr;
+    }
+
+    if (NS_WARN_IF(!mGlobals.Put(origin, globalObject, fallible))) {
+      return nullptr;
+    }
+
+    return globalObject;
+  }
+
+ private:
+  nsInterfaceHashtable<nsCStringHashKey, nsIGlobalObject> mGlobals;
+};
+
+struct StringWriteFunc final : public JSONWriteFunc {
+  nsACString&
+      mBuffer;  // The lifetime of the struct must be bound to the buffer
+  explicit StringWriteFunc(nsACString& aBuffer) : mBuffer(aBuffer) {}
+
+  void Write(const char* aStr) override { mBuffer.Append(aStr); }
+};
+
+class ReportJSONWriter final : public JSONWriter {
+ public:
+  explicit ReportJSONWriter(nsACString& aOutput)
+      : JSONWriter(MakeUnique<StringWriteFunc>(aOutput)) {}
+
+  void JSONProperty(const char* aProperty, const char* aJSON) {
+    Separator();
+    PropertyNameAndColon(aProperty);
+    mWriter->Write(aJSON);
+  }
+};
+
+void SendReport(ReportDeliver::ReportData& aReportData,
+                SandboxGlobalHolder& aHolder) {
+  nsCOMPtr<nsIGlobalObject> globalObject =
+      aHolder.GetOrCreateSandboxGlobalObject(aReportData.mPrincipal);
+  if (NS_WARN_IF(!globalObject)) {
+    return;
+  }
+
+  // The body
+  nsAutoCString body;
+  ReportJSONWriter w(body);
+
+  w.Start();
+
+  w.IntProperty(
+      "age", (TimeStamp::Now() - aReportData.mCreationTime).ToMilliseconds());
+  w.StringProperty("type", NS_ConvertUTF16toUTF8(aReportData.mType).get());
+  w.StringProperty("url", NS_ConvertUTF16toUTF8(aReportData.mURL).get());
+  w.StringProperty("user_agent",
+                   NS_ConvertUTF16toUTF8(aReportData.mUserAgent).get());
+  w.JSONProperty("body", aReportData.mReportBodyJSON.get());
+  w.End();
+
+  // The body as stream
+  nsCOMPtr<nsIInputStream> streamBody;
+  nsresult rv = NS_NewCStringInputStream(getter_AddRefs(streamBody), body);
+
+  // Headers
+  IgnoredErrorResult error;
+  RefPtr<InternalHeaders> internalHeaders =
+      new InternalHeaders(HeadersGuardEnum::Request);
+  internalHeaders->Set(NS_LITERAL_CSTRING("Content-Type"),
+                       NS_LITERAL_CSTRING("application/reports+json"), error);
+  if (NS_WARN_IF(error.Failed())) {
+    return;
+  }
+
+  // URL and fragments
+  nsCOMPtr<nsIURI> uri;
+  rv = NS_NewURI(getter_AddRefs(uri),
+                 NS_ConvertUTF8toUTF16(aReportData.mEndpointURL), nullptr,
+                 nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> uriClone;
+  rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriClone));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsAutoCString uriSpec;
+  rv = uriClone->GetSpec(uriSpec);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsAutoCString uriFragment;
+  rv = uri->GetRef(uriFragment);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  RefPtr<InternalRequest> internalRequest =
+      new InternalRequest(uriSpec, uriFragment);
+
+  internalRequest->SetMethod(NS_LITERAL_CSTRING("POST"));
+  internalRequest->SetBody(streamBody, body.Length());
+  internalRequest->SetHeaders(internalHeaders);
+  internalRequest->SetSkipServiceWorker();
+  // TODO: internalRequest->SetContentPolicyType(TYPE_REPORT);
+  internalRequest->SetMode(RequestMode::Cors);
+  internalRequest->SetCredentialsMode(RequestCredentials::Include);
+
+  RefPtr<Request> request = new Request(globalObject, internalRequest, nullptr);
+
+  RequestOrUSVString fetchInput;
+  fetchInput.SetAsRequest() = request;
+
+  RefPtr<Promise> promise = FetchRequest(
+      globalObject, fetchInput, RequestInit(), CallerType::NonSystem, error);
+  if (error.Failed()) {
+    ++aReportData.mFailures;
+    if (gReportDeliver) {
+      gReportDeliver->AppendReportData(aReportData);
+    }
+    return;
+  }
+
+  RefPtr<ReportFetchHandler> handler = new ReportFetchHandler(aReportData);
+  promise->AppendNativeHandler(handler);
+}
+
+}  // namespace
+
 /* static */ void ReportDeliver::Record(nsPIDOMWindowInner* aWindow,
                                         const nsAString& aType,
                                         const nsAString& aGroupName,
                                         const nsAString& aURL,
                                         ReportBody* aBody) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(aBody);
 
-  nsAutoString reportBodyJSON;
-  aBody->ToJSON(reportBodyJSON);
+  nsAutoCString reportBodyJSON;
+  ReportJSONWriter w(reportBodyJSON);
+
+  w.Start();
+  aBody->ToJSON(w);
+  w.End();
 
   nsCOMPtr<nsIPrincipal> principal =
       nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
   if (NS_WARN_IF(!principal)) {
     return;
   }
 
   mozilla::ipc::PrincipalInfo principalInfo;
@@ -46,21 +278,112 @@ namespace dom {
                                                     principalInfo);
   if (NS_WARN_IF(!actor)) {
     return;
   }
 
   ReportData data;
   data.mType = aType;
   data.mURL = aURL;
+  data.mCreationTime = TimeStamp::Now();
   data.mReportBodyJSON = reportBodyJSON;
   data.mPrincipal = principal;
   data.mFailures = 0;
 
+  Navigator* navigator = aWindow->Navigator();
+  MOZ_ASSERT(navigator);
+
+  IgnoredErrorResult error;
+  navigator->GetUserAgent(data.mUserAgent, CallerType::NonSystem, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return;
+  }
+
   static_cast<EndpointForReportChild*>(actor)->Initialize(data);
 }
 
 /* static */ void ReportDeliver::Fetch(const ReportData& aReportData) {
-  // TODO
+  if (!gReportDeliver) {
+    RefPtr<ReportDeliver> rd = new ReportDeliver();
+
+    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+    if (NS_WARN_IF(!obs)) {
+      return;
+    }
+
+    obs->AddObserver(rd, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+    gReportDeliver = rd;
+  }
+
+  gReportDeliver->AppendReportData(aReportData);
+}
+
+void ReportDeliver::AppendReportData(const ReportData& aReportData) {
+  if (aReportData.mFailures >
+      StaticPrefs::dom_reporting_delivering_maxFailures()) {
+    return;
+  }
+
+  if (NS_WARN_IF(!mReportQueue.AppendElement(aReportData, fallible))) {
+    return;
+  }
+
+  if (!mTimer) {
+    uint32_t timeout = StaticPrefs::dom_reporting_delivering_timeout() * 1000;
+    nsresult rv = NS_NewTimerWithCallback(
+        getter_AddRefs(mTimer), this, timeout, nsITimer::TYPE_ONE_SHOT,
+        SystemGroup::EventTargetFor(TaskCategory::Other));
+    Unused << NS_WARN_IF(NS_FAILED(rv));
+  }
 }
 
+NS_IMETHODIMP
+ReportDeliver::Notify(nsITimer* aTimer) {
+  mTimer = nullptr;
+
+  nsTArray<ReportData> reports;
+  reports.SwapElements(mReportQueue);
+
+  SandboxGlobalHolder holder;
+
+  for (ReportData& report : reports) {
+    SendReport(report, holder);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ReportDeliver::Observe(nsISupports* aSubject, const char* aTopic,
+                       const char16_t* aData) {
+  MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (NS_WARN_IF(!obs)) {
+    return NS_OK;
+  }
+
+  obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  gReportDeliver = nullptr;
+  return NS_OK;
+}
+
+ReportDeliver::ReportDeliver() = default;
+
+ReportDeliver::~ReportDeliver() = default;
+
+NS_INTERFACE_MAP_BEGIN(ReportDeliver)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(ReportDeliver)
+NS_IMPL_RELEASE(ReportDeliver)
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/reporting/ReportDeliver.h
+++ b/dom/reporting/ReportDeliver.h
@@ -2,36 +2,55 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_ReportDeliver_h
 #define mozilla_dom_ReportDeliver_h
 
+#include "nsIObserver.h"
+#include "nsITimer.h"
+
 class nsIPrincipal;
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 namespace dom {
 
-class ReportDeliver final {
+class ReportDeliver final : public nsIObserver, public nsITimerCallback {
  public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSITIMERCALLBACK
+
   struct ReportData {
     nsString mType;
     nsString mURL;
     nsCString mEndpointURL;
-    nsString mReportBodyJSON;
+    nsString mUserAgent;
+    TimeStamp mCreationTime;
+    nsCString mReportBodyJSON;
     nsCOMPtr<nsIPrincipal> mPrincipal;
     uint32_t mFailures;
   };
 
   static void Record(nsPIDOMWindowInner* aWindow, const nsAString& aType,
                      const nsAString& aGroupName, const nsAString& aURL,
                      ReportBody* aBody);
 
   static void Fetch(const ReportData& aReportData);
+
+  void AppendReportData(const ReportData& aReportData);
+
+ private:
+  ReportDeliver();
+  ~ReportDeliver();
+
+  nsTArray<ReportData> mReportQueue;
+
+  nsCOMPtr<nsITimer> mTimer;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_ReportDeliver_h
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1909,13 +1909,27 @@ VARCACHE_PREF(
 #undef PREF_VALUE
 
 VARCACHE_PREF(
   "dom.reporting.header.enabled",
    dom_reporting_header_enabled,
   RelaxedAtomicBool, false
 )
 
+// Any X seconds the reports are dispatched to endpoints.
+VARCACHE_PREF(
+  "dom.reporting.delivering.timeout",
+   dom_reporting_delivering_timeout,
+  uint32_t, 5
+)
+
+// How many times the delivering of a report should be tried.
+VARCACHE_PREF(
+  "dom.reporting.delivering.maxFailures",
+   dom_reporting_delivering_maxFailures,
+  uint32_t, 3
+)
+
 //---------------------------------------------------------------------------
 // End of prefs
 //---------------------------------------------------------------------------
 
 // clang-format on