Bug 1508310 - Implement Report-to header support - part 6 - Remove endpoints, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Sat, 01 Dec 2018 21:26:09 +0100
changeset 505580 f2492c85277752cf0e0aa6a897e8ac975522f04c
parent 505579 a00225dfc4146cbe04d03c28de49d4fed41cf04c
child 505581 696af9c24623fb963ebc25b5e21ca9caf5055077
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 6 - Remove endpoints, r=smaug
dom/reporting/ReportDeliver.cpp
dom/reporting/ReportDeliver.h
dom/reporting/ReportingHeader.cpp
dom/reporting/ReportingHeader.h
dom/reporting/tests/gtest/TestReportToParser.cpp
ipc/glue/BackgroundParentImpl.cpp
ipc/glue/BackgroundParentImpl.h
ipc/glue/PBackground.ipdl
--- a/dom/reporting/ReportDeliver.cpp
+++ b/dom/reporting/ReportDeliver.cpp
@@ -48,18 +48,28 @@ class ReportFetchHandler final : public 
 
     {
       Response* response = nullptr;
       if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Response, &obj, response)))) {
         return;
       }
 
       if (response->Status() == 410) {
-        // TODO: remove
-        return;
+        mozilla::ipc::PBackgroundChild* actorChild =
+            mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+
+        mozilla::ipc::PrincipalInfo principalInfo;
+        nsresult rv =
+            PrincipalToPrincipalInfo(mReportData.mPrincipal, &principalInfo);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return;
+        }
+
+        actorChild->SendRemoveEndpoint(mReportData.mGroupName,
+                                       mReportData.mEndpointURL, principalInfo);
       }
     }
   }
 
   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
     if (gReportDeliver) {
       ++mReportData.mFailures;
       gReportDeliver->AppendReportData(mReportData);
@@ -277,16 +287,17 @@ void SendReport(ReportDeliver::ReportDat
       actorChild->SendPEndpointForReportConstructor(nsString(aGroupName),
                                                     principalInfo);
   if (NS_WARN_IF(!actor)) {
     return;
   }
 
   ReportData data;
   data.mType = aType;
+  data.mGroupName = aGroupName;
   data.mURL = aURL;
   data.mCreationTime = TimeStamp::Now();
   data.mReportBodyJSON = reportBodyJSON;
   data.mPrincipal = principal;
   data.mFailures = 0;
 
   Navigator* navigator = aWindow->Navigator();
   MOZ_ASSERT(navigator);
--- a/dom/reporting/ReportDeliver.h
+++ b/dom/reporting/ReportDeliver.h
@@ -19,16 +19,17 @@ namespace dom {
 class ReportDeliver final : public nsIObserver, public nsITimerCallback {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSITIMERCALLBACK
 
   struct ReportData {
     nsString mType;
+    nsString mGroupName;
     nsString mURL;
     nsCString mEndpointURL;
     nsString mUserAgent;
     TimeStamp mCreationTime;
     nsCString mReportBodyJSON;
     nsCOMPtr<nsIPrincipal> mPrincipal;
     uint32_t mFailures;
   };
--- a/dom/reporting/ReportingHeader.cpp
+++ b/dom/reporting/ReportingHeader.cpp
@@ -231,17 +231,20 @@ void ReportingHeader::ReportingFromChann
     uint32_t endpointsLength;
     if (!JS_GetArrayLength(cx, endpoints, &endpointsLength) ||
         endpointsLength == 0) {
       LogToConsoleIncompleteItem(aChannel, aURI, groupName);
       continue;
     }
 
     bool found = false;
-    for (const Group& group : client->mGroups) {
+    nsTObserverArray<Group>::ForwardIterator iter(client->mGroups);
+    while (iter.HasMore()) {
+      const Group& group = iter.GetNext();
+
       if (group.mName == groupName) {
         found = true;
         break;
       }
     }
 
     if (found) {
       LogToConsoleDuplicateGroup(aChannel, aURI, groupName);
@@ -433,17 +436,20 @@ bool ReportingHeader::IsSecureURI(nsIURI
     return;
   }
 
   Client* client = gReporting->mOrigins.Get(origin);
   if (!client) {
     return;
   }
 
-  for (const Group& group : client->mGroups) {
+  nsTObserverArray<Group>::ForwardIterator iter(client->mGroups);
+  while (iter.HasMore()) {
+    const Group& group = iter.GetNext();
+
     if (group.mName == aGroupName) {
       GetEndpointForReportInternal(group, aEndpointURI);
       break;
     }
   }
 }
 
 /* static */ void ReportingHeader::GetEndpointForReportInternal(
@@ -456,17 +462,20 @@ bool ReportingHeader::IsSecureURI(nsIURI
 
   if (aGroup.mEndpoints.IsEmpty()) {
     return;
   }
 
   int64_t minPriority = -1;
   uint32_t totalWeight = 0;
 
-  for (const Endpoint& endpoint : aGroup.mEndpoints) {
+  nsTObserverArray<Endpoint>::ForwardIterator iter(aGroup.mEndpoints);
+  while (iter.HasMore()) {
+    const Endpoint& endpoint = iter.GetNext();
+
     if (minPriority == -1 || minPriority > endpoint.mPriority) {
       minPriority = endpoint.mPriority;
       totalWeight = endpoint.mWeight;
     } else if (minPriority == endpoint.mPriority) {
       totalWeight += endpoint.mWeight;
     }
   }
 
@@ -485,24 +494,98 @@ bool ReportingHeader::IsSecureURI(nsIURI
     return;
   }
 
   memcpy(&randomNumber, buffer, sizeof(randomNumber));
   free(buffer);
 
   totalWeight = randomNumber % totalWeight;
 
-  for (const Endpoint& endpoint : aGroup.mEndpoints) {
+  nsTObserverArray<Endpoint>::ForwardIterator iter2(aGroup.mEndpoints);
+  while (iter2.HasMore()) {
+    const Endpoint& endpoint = iter2.GetNext();
+
     if (minPriority == endpoint.mPriority && totalWeight < endpoint.mWeight) {
       Unused << NS_WARN_IF(NS_FAILED(endpoint.mUrl->GetSpec(aEndpointURI)));
       break;
     }
   }
 }
 
+/* static */ void ReportingHeader::RemoveEndpoint(
+    const nsAString& aGroupName, const nsACString& aEndpointURL,
+    const mozilla::ipc::PrincipalInfo& aPrincipalInfo) {
+  if (!gReporting) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), aEndpointURL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(aPrincipalInfo);
+  if (NS_WARN_IF(!principal)) {
+    return;
+  }
+
+  nsAutoCString origin;
+  rv = principal->GetOrigin(origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  Client* client = gReporting->mOrigins.Get(origin);
+  if (!client) {
+    return;
+  }
+
+  // Scope for the group iterator.
+  {
+    nsTObserverArray<Group>::BackwardIterator iter(client->mGroups);
+    while (iter.HasMore()) {
+      const Group& group = iter.GetNext();
+      if (group.mName != aGroupName) {
+        continue;
+      }
+
+      // Scope for the endpoint iterator.
+      {
+        nsTObserverArray<Endpoint>::BackwardIterator endpointIter(
+            group.mEndpoints);
+        while (endpointIter.HasMore()) {
+          const Endpoint& endpoint = endpointIter.GetNext();
+
+          bool equal = false;
+          rv = endpoint.mUrl->Equals(uri, &equal);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            continue;
+          }
+
+          if (equal) {
+            endpointIter.Remove();
+            break;
+          }
+        }
+      }
+
+      if (group.mEndpoints.IsEmpty()) {
+        iter.Remove();
+      }
+
+      break;
+    }
+  }
+
+  if (client->mGroups.IsEmpty()) {
+    gReporting->mOrigins.Remove(origin);
+  }
+}
+
 NS_INTERFACE_MAP_BEGIN(ReportingHeader)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(ReportingHeader)
 NS_IMPL_RELEASE(ReportingHeader)
 
--- a/dom/reporting/ReportingHeader.h
+++ b/dom/reporting/ReportingHeader.h
@@ -5,17 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_ReportingHeader_h
 #define mozilla_dom_ReportingHeader_h
 
 #include "mozilla/TimeStamp.h"
 #include "nsClassHashtable.h"
 #include "nsIObserver.h"
-#include "nsTArray.h"
+#include "nsTObserverArray.h"
 
 class nsIHttpChannel;
 class nsIPrincipal;
 class nsIURI;
 
 namespace mozilla {
 
 namespace ipc {
@@ -39,31 +39,35 @@ class ReportingHeader final : public nsI
     uint32_t mWeight;
   };
 
   struct Group {
     nsString mName;
     bool mIncludeSubdomains;
     int32_t mTTL;
     TimeStamp mCreationTime;
-    nsTArray<Endpoint> mEndpoints;
+    nsTObserverArray<Endpoint> mEndpoints;
   };
 
   struct Client {
-    nsTArray<Group> mGroups;
+    nsTObserverArray<Group> mGroups;
   };
 
   static UniquePtr<Client> ParseHeader(nsIHttpChannel* aChannel, nsIURI* aURI,
                                        const nsACString& aHeaderValue);
 
   static void GetEndpointForReport(
       const nsAString& aGroupName,
       const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
       nsACString& aEndpointURI);
 
+  static void RemoveEndpoint(const nsAString& aGroupName,
+                             const nsACString& aEndpointURL,
+                             const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+
  private:
   ReportingHeader();
   ~ReportingHeader();
 
   static void Shutdown();
 
   // Checks if a channel contains a Report-To header and parses its value.
   void ReportingFromChannel(nsIHttpChannel* aChannel);
--- a/dom/reporting/tests/gtest/TestReportToParser.cpp
+++ b/dom/reporting/tests/gtest/TestReportToParser.cpp
@@ -37,343 +37,383 @@ TEST(ReportToParser, Basic) {
   // Single client
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 42, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_TRUE(client->mGroups[0].mName.EqualsLiteral("default"));
-  ASSERT_FALSE(client->mGroups[0].mIncludeSubdomains);
-  ASSERT_EQ(42, client->mGroups[0].mTTL);
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints.Length());
-  ASSERT_TRUE(NS_SUCCEEDED(client->mGroups[0].mEndpoints[0].mUrl->Equals(
-                  uri, &urlEqual)) &&
-              urlEqual);
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints[0].mPriority);
-  ASSERT_EQ((uint32_t)2, client->mGroups[0].mEndpoints[0].mWeight);
+  ASSERT_TRUE(client->mGroups.ElementAt(0).mName.EqualsLiteral("default"));
+  ASSERT_FALSE(client->mGroups.ElementAt(0).mIncludeSubdomains);
+  ASSERT_EQ(42, client->mGroups.ElementAt(0).mTTL);
+  ASSERT_EQ((uint32_t)1, client->mGroups.ElementAt(0).mEndpoints.Length());
+  ASSERT_TRUE(
+      NS_SUCCEEDED(
+          client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mUrl->Equals(
+              uri, &urlEqual)) &&
+      urlEqual);
+  ASSERT_EQ((uint32_t)1,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mPriority);
+  ASSERT_EQ((uint32_t)2,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mWeight);
 
   // 2 clients, same group name.
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 43, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]},"
           "{\"max_age\": 44, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_TRUE(client->mGroups[0].mName.EqualsLiteral("default"));
-  ASSERT_EQ(43, client->mGroups[0].mTTL);
+  ASSERT_TRUE(client->mGroups.ElementAt(0).mName.EqualsLiteral("default"));
+  ASSERT_EQ(43, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first one with an invalid group name.
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 43, \"group\": 123, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]},"
           "{\"max_age\": 44, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_TRUE(client->mGroups[0].mName.EqualsLiteral("default"));
-  ASSERT_EQ(44, client->mGroups[0].mTTL);
+  ASSERT_TRUE(client->mGroups.ElementAt(0).mName.EqualsLiteral("default"));
+  ASSERT_EQ(44, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first one with an invalid group name.
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 43, \"group\": null, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]},"
           "{\"max_age\": 44, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_TRUE(client->mGroups[0].mName.EqualsLiteral("default"));
-  ASSERT_EQ(44, client->mGroups[0].mTTL);
+  ASSERT_TRUE(client->mGroups.ElementAt(0).mName.EqualsLiteral("default"));
+  ASSERT_EQ(44, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first one with an invalid group name.
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 43, \"group\": {}, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]},"
           "{\"max_age\": 44, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_TRUE(client->mGroups[0].mName.EqualsLiteral("default"));
-  ASSERT_EQ(44, client->mGroups[0].mTTL);
+  ASSERT_TRUE(client->mGroups.ElementAt(0).mName.EqualsLiteral("default"));
+  ASSERT_EQ(44, client->mGroups.ElementAt(0).mTTL);
 
   // Single client: optional params
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 45, \"group\": \"foobar\", \"include_subdomains\": "
           "true, \"endpoints\": [{\"url\": \"https://example.com\", "
           "\"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_TRUE(client->mGroups[0].mName.EqualsLiteral("foobar"));
-  ASSERT_TRUE(client->mGroups[0].mIncludeSubdomains);
-  ASSERT_EQ(45, client->mGroups[0].mTTL);
+  ASSERT_TRUE(client->mGroups.ElementAt(0).mName.EqualsLiteral("foobar"));
+  ASSERT_TRUE(client->mGroups.ElementAt(0).mIncludeSubdomains);
+  ASSERT_EQ(45, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first incomplete: missing max_age.
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"endpoints\": [{\"url\": \"https://example.com\", \"priority\": "
           "1, \"weight\": 2}]},"
           "{\"max_age\": 46, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ(46, client->mGroups[0].mTTL);
+  ASSERT_EQ(46, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first incomplete: invalid max_age.
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": null, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]},"
           "{\"max_age\": 46, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ(46, client->mGroups[0].mTTL);
+  ASSERT_EQ(46, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first incomplete: invalid max_age.
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": \"foobar\", \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]},"
           "{\"max_age\": 46, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ(46, client->mGroups[0].mTTL);
+  ASSERT_EQ(46, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first incomplete: invalid max_age.
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": {}, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]},"
           "{\"max_age\": 46, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ(46, client->mGroups[0].mTTL);
+  ASSERT_EQ(46, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first incomplete: missing endpoints
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 47},"
           "{\"max_age\": 48, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ(48, client->mGroups[0].mTTL);
+  ASSERT_EQ(48, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first incomplete: invalid endpoints
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 47, \"endpoints\": null },"
           "{\"max_age\": 48, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ(48, client->mGroups[0].mTTL);
+  ASSERT_EQ(48, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first incomplete: invalid endpoints
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 47, \"endpoints\": \"abc\" },"
           "{\"max_age\": 48, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ(48, client->mGroups[0].mTTL);
+  ASSERT_EQ(48, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first incomplete: invalid endpoints
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 47, \"endpoints\": 42 },"
           "{\"max_age\": 48, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ(48, client->mGroups[0].mTTL);
+  ASSERT_EQ(48, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first incomplete: invalid endpoints
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 47, \"endpoints\": {} },"
           "{\"max_age\": 48, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ(48, client->mGroups[0].mTTL);
+  ASSERT_EQ(48, client->mGroups.ElementAt(0).mTTL);
 
   // 2 clients, the first incomplete: empty endpoints
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 49, \"endpoints\": []},"
           "{\"max_age\": 50, \"endpoints\": [{\"url\": "
           "\"https://example.com\", \"priority\": 1, \"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ(50, client->mGroups[0].mTTL);
+  ASSERT_EQ(50, client->mGroups.ElementAt(0).mTTL);
 
   // 2 endpoints, the first incomplete: missing url
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING("{\"max_age\": 51, \"endpoints\": ["
                          " {\"priority\": 1, \"weight\": 2},"
                          " {\"url\": \"https://example.com\", \"priority\": 1, "
                          "\"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints.Length());
-  ASSERT_TRUE(NS_SUCCEEDED(client->mGroups[0].mEndpoints[0].mUrl->Equals(
-                  uri, &urlEqual)) &&
-              urlEqual);
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints[0].mPriority);
-  ASSERT_EQ((uint32_t)2, client->mGroups[0].mEndpoints[0].mWeight);
+  ASSERT_EQ((uint32_t)1, client->mGroups.ElementAt(0).mEndpoints.Length());
+  ASSERT_TRUE(
+      NS_SUCCEEDED(
+          client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mUrl->Equals(
+              uri, &urlEqual)) &&
+      urlEqual);
+  ASSERT_EQ((uint32_t)1,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mPriority);
+  ASSERT_EQ((uint32_t)2,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mWeight);
 
   // 2 endpoints, the first incomplete: invalid url
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING("{\"max_age\": 51, \"endpoints\": ["
                          " {\"url\": 42, \"priority\": 1, \"weight\": 2},"
                          " {\"url\": \"https://example.com\", \"priority\": 1, "
                          "\"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints.Length());
-  ASSERT_TRUE(NS_SUCCEEDED(client->mGroups[0].mEndpoints[0].mUrl->Equals(
-                  uri, &urlEqual)) &&
-              urlEqual);
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints[0].mPriority);
-  ASSERT_EQ((uint32_t)2, client->mGroups[0].mEndpoints[0].mWeight);
+  ASSERT_EQ((uint32_t)1, client->mGroups.ElementAt(0).mEndpoints.Length());
+  ASSERT_TRUE(
+      NS_SUCCEEDED(
+          client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mUrl->Equals(
+              uri, &urlEqual)) &&
+      urlEqual);
+  ASSERT_EQ((uint32_t)1,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mPriority);
+  ASSERT_EQ((uint32_t)2,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mWeight);
 
   // 2 endpoints, the first incomplete: invalid url
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 51, \"endpoints\": ["
           " {\"url\": \"something here\", \"priority\": 1, \"weight\": 2},"
           " {\"url\": \"https://example.com\", \"priority\": 1, \"weight\": "
           "2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints.Length());
-  ASSERT_TRUE(NS_SUCCEEDED(client->mGroups[0].mEndpoints[0].mUrl->Equals(
-                  uri, &urlEqual)) &&
-              urlEqual);
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints[0].mPriority);
-  ASSERT_EQ((uint32_t)2, client->mGroups[0].mEndpoints[0].mWeight);
+  ASSERT_EQ((uint32_t)1, client->mGroups.ElementAt(0).mEndpoints.Length());
+  ASSERT_TRUE(
+      NS_SUCCEEDED(
+          client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mUrl->Equals(
+              uri, &urlEqual)) &&
+      urlEqual);
+  ASSERT_EQ((uint32_t)1,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mPriority);
+  ASSERT_EQ((uint32_t)2,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mWeight);
 
   // 2 endpoints, the first incomplete: invalid url
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING("{\"max_age\": 51, \"endpoints\": ["
                          " {\"url\": {}, \"priority\": 1, \"weight\": 2},"
                          " {\"url\": \"https://example.com\", \"priority\": 1, "
                          "\"weight\": 2}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints.Length());
-  ASSERT_TRUE(NS_SUCCEEDED(client->mGroups[0].mEndpoints[0].mUrl->Equals(
-                  uri, &urlEqual)) &&
-              urlEqual);
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints[0].mPriority);
-  ASSERT_EQ((uint32_t)2, client->mGroups[0].mEndpoints[0].mWeight);
+  ASSERT_EQ((uint32_t)1, client->mGroups.ElementAt(0).mEndpoints.Length());
+  ASSERT_TRUE(
+      NS_SUCCEEDED(
+          client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mUrl->Equals(
+              uri, &urlEqual)) &&
+      urlEqual);
+  ASSERT_EQ((uint32_t)1,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mPriority);
+  ASSERT_EQ((uint32_t)2,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mWeight);
 
   // 2 endpoints, the first incomplete: missing priority
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 52, \"endpoints\": ["
           " {\"url\": \"https://example.com\", \"weight\": 3}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints.Length());
-  ASSERT_TRUE(NS_SUCCEEDED(client->mGroups[0].mEndpoints[0].mUrl->Equals(
-                  uri, &urlEqual)) &&
-              urlEqual);
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints[0].mPriority);
-  ASSERT_EQ((uint32_t)3, client->mGroups[0].mEndpoints[0].mWeight);
+  ASSERT_EQ((uint32_t)1, client->mGroups.ElementAt(0).mEndpoints.Length());
+  ASSERT_TRUE(
+      NS_SUCCEEDED(
+          client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mUrl->Equals(
+              uri, &urlEqual)) &&
+      urlEqual);
+  ASSERT_EQ((uint32_t)1,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mPriority);
+  ASSERT_EQ((uint32_t)3,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mWeight);
 
   // 2 endpoints, the first incomplete: invalid priority
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING("{\"max_age\": 52, \"endpoints\": ["
                          " {\"url\": \"https://example.com\", \"priority\": "
                          "{}, \"weight\": 2},"
                          " {\"url\": \"https://example.com\", \"priority\": 2, "
                          "\"weight\": 3}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints.Length());
-  ASSERT_TRUE(NS_SUCCEEDED(client->mGroups[0].mEndpoints[0].mUrl->Equals(
-                  uri, &urlEqual)) &&
-              urlEqual);
-  ASSERT_EQ((uint32_t)2, client->mGroups[0].mEndpoints[0].mPriority);
-  ASSERT_EQ((uint32_t)3, client->mGroups[0].mEndpoints[0].mWeight);
+  ASSERT_EQ((uint32_t)1, client->mGroups.ElementAt(0).mEndpoints.Length());
+  ASSERT_TRUE(
+      NS_SUCCEEDED(
+          client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mUrl->Equals(
+              uri, &urlEqual)) &&
+      urlEqual);
+  ASSERT_EQ((uint32_t)2,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mPriority);
+  ASSERT_EQ((uint32_t)3,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mWeight);
 
   // 2 endpoints, the first incomplete: invalid priority
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING("{\"max_age\": 52, \"endpoints\": ["
                          " {\"url\": \"https://example.com\", \"priority\": "
                          "\"ok\", \"weight\": 2},"
                          " {\"url\": \"https://example.com\", \"priority\": 2, "
                          "\"weight\": 3}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints.Length());
-  ASSERT_TRUE(NS_SUCCEEDED(client->mGroups[0].mEndpoints[0].mUrl->Equals(
-                  uri, &urlEqual)) &&
-              urlEqual);
-  ASSERT_EQ((uint32_t)2, client->mGroups[0].mEndpoints[0].mPriority);
-  ASSERT_EQ((uint32_t)3, client->mGroups[0].mEndpoints[0].mWeight);
+  ASSERT_EQ((uint32_t)1, client->mGroups.ElementAt(0).mEndpoints.Length());
+  ASSERT_TRUE(
+      NS_SUCCEEDED(
+          client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mUrl->Equals(
+              uri, &urlEqual)) &&
+      urlEqual);
+  ASSERT_EQ((uint32_t)2,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mPriority);
+  ASSERT_EQ((uint32_t)3,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mWeight);
 
   // 2 endpoints, the first incomplete: missing weight
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING(
           "{\"max_age\": 52, \"endpoints\": ["
           " {\"url\": \"https://example.com\", \"priority\": 5}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints.Length());
-  ASSERT_TRUE(NS_SUCCEEDED(client->mGroups[0].mEndpoints[0].mUrl->Equals(
-                  uri, &urlEqual)) &&
-              urlEqual);
-  ASSERT_EQ((uint32_t)5, client->mGroups[0].mEndpoints[0].mPriority);
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints[0].mWeight);
+  ASSERT_EQ((uint32_t)1, client->mGroups.ElementAt(0).mEndpoints.Length());
+  ASSERT_TRUE(
+      NS_SUCCEEDED(
+          client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mUrl->Equals(
+              uri, &urlEqual)) &&
+      urlEqual);
+  ASSERT_EQ((uint32_t)5,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mPriority);
+  ASSERT_EQ((uint32_t)1,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mWeight);
 
   // 2 endpoints, the first incomplete: invalid weight
   client = ReportingHeader::ParseHeader(
       nullptr, uri,
       NS_LITERAL_CSTRING("{\"max_age\": 52, \"endpoints\": ["
                          " {\"url\": \"https://example.com\", \"priority\": 4, "
                          "\"weight\": []},"
                          " {\"url\": \"https://example.com\", \"priority\": 5, "
                          "\"weight\": 6}]}"));
   ASSERT_TRUE(!!client);
   ASSERT_EQ((uint32_t)1, client->mGroups.Length());
-  ASSERT_EQ((uint32_t)1, client->mGroups[0].mEndpoints.Length());
-  ASSERT_TRUE(NS_SUCCEEDED(client->mGroups[0].mEndpoints[0].mUrl->Equals(
-                  uri, &urlEqual)) &&
-              urlEqual);
-  ASSERT_EQ((uint32_t)5, client->mGroups[0].mEndpoints[0].mPriority);
-  ASSERT_EQ((uint32_t)6, client->mGroups[0].mEndpoints[0].mWeight);
+  ASSERT_EQ((uint32_t)1, client->mGroups.ElementAt(0).mEndpoints.Length());
+  ASSERT_TRUE(
+      NS_SUCCEEDED(
+          client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mUrl->Equals(
+              uri, &urlEqual)) &&
+      urlEqual);
+  ASSERT_EQ((uint32_t)5,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mPriority);
+  ASSERT_EQ((uint32_t)6,
+            client->mGroups.ElementAt(0).mEndpoints.ElementAt(0).mWeight);
 }
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -34,16 +34,17 @@
 #include "mozilla/dom/ipc/IPCBlobInputStreamParent.h"
 #include "mozilla/dom/ipc/PendingIPCBlobParent.h"
 #include "mozilla/dom/ipc/TemporaryIPCBlobParent.h"
 #include "mozilla/dom/localstorage/ActorsParent.h"
 #include "mozilla/dom/quota/ActorsParent.h"
 #include "mozilla/dom/simpledb/ActorsParent.h"
 #include "mozilla/dom/RemoteWorkerParent.h"
 #include "mozilla/dom/RemoteWorkerServiceParent.h"
+#include "mozilla/dom/ReportingHeader.h"
 #include "mozilla/dom/SharedWorkerParent.h"
 #include "mozilla/dom/StorageIPC.h"
 #include "mozilla/dom/MIDIManagerParent.h"
 #include "mozilla/dom/MIDIPortParent.h"
 #include "mozilla/dom/MIDIPlatformService.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/IPCStreamAlloc.h"
@@ -1222,15 +1223,28 @@ mozilla::ipc::IPCResult BackgroundParent
 
 bool BackgroundParentImpl::DeallocPEndpointForReportParent(
     PEndpointForReportParent* aActor) {
   RefPtr<dom::EndpointForReportParent> actor =
       dont_AddRef(static_cast<dom::EndpointForReportParent*>(aActor));
   return true;
 }
 
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvRemoveEndpoint(
+    const nsString& aGroupName, const nsCString& aEndpointURL,
+    const PrincipalInfo& aPrincipalInfo) {
+  NS_DispatchToMainThread(
+      NS_NewRunnableFunction("BackgroundParentImpl::RecvRemoveEndpoint(",
+                             [aGroupName, aEndpointURL, aPrincipalInfo]() {
+                               dom::ReportingHeader::RemoveEndpoint(
+                                   aGroupName, aEndpointURL, aPrincipalInfo);
+                             }));
+
+  return IPC_OK();
+}
+
 }  // namespace ipc
 }  // namespace mozilla
 
 void TestParent::ActorDestroy(ActorDestroyReason aWhy) {
   mozilla::ipc::AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
 }
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -362,14 +362,18 @@ class BackgroundParentImpl : public PBac
       const nsString& aGroupName, const PrincipalInfo& aPrincipalInfo) override;
 
   virtual mozilla::ipc::IPCResult RecvPEndpointForReportConstructor(
       PEndpointForReportParent* actor, const nsString& aGroupName,
       const PrincipalInfo& aPrincipalInfo) override;
 
   virtual bool DeallocPEndpointForReportParent(
       PEndpointForReportParent* aActor) override;
+
+  virtual mozilla::ipc::IPCResult RecvRemoveEndpoint(
+      const nsString& aGroupName, const nsCString& aEndpointURL,
+      const PrincipalInfo& aPrincipalInfo) override;
 };
 
 }  // namespace ipc
 }  // namespace mozilla
 
 #endif  // mozilla_ipc_backgroundparentimpl_h__
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -212,16 +212,19 @@ parent:
   async PRemoteWorkerService();
 
   async PServiceWorkerContainer();
 
   async PServiceWorkerRegistration(IPCServiceWorkerRegistrationDescriptor aDescriptor);
 
   async PEndpointForReport(nsString aGroupName, PrincipalInfo aPrincipalInfo);
 
+  async RemoveEndpoint(nsString aGroupName, nsCString aEndpointURL,
+                       PrincipalInfo aPrincipalInfo);
+
 child:
   async PCache();
   async PCacheStreamControl();
 
   async PParentToChildStream();
 
   async PPendingIPCBlob(IPCBlob blob);