commit 8f2a3fac38ddb47db9216164a710c092b278720d
parent 1f2e70f323be31fb98a9cfa0dc14579c347d6d79
Author: Simon Farre <simon.farre.cx@gmail.com>
Date: Wed, 7 Jan 2026 09:01:07 +0000
Bug 2003729 - Buffer reports by type separately r=smaug
When the spec wants to evict reports due to a maxmimum amount having
been exceeded, it does so by report type.
This adds the ability to store max of N reports per type.
Also added a web platform test to test for ReportingObserver returning
test in the correct order as they were added.
See step 4 of 4.2:
https://www.w3.org/TR/reporting-1/#notify-observers
Differential Revision: https://phabricator.services.mozilla.com/D276831
Diffstat:
7 files changed, 127 insertions(+), 28 deletions(-)
diff --git a/dom/base/nsIGlobalObject.cpp b/dom/base/nsIGlobalObject.cpp
@@ -26,9 +26,6 @@
#include "nsGlobalWindowInner.h"
#include "nsThreadUtils.h"
-// Max number of Report objects
-constexpr auto MAX_REPORT_RECORDS = 100;
-
using mozilla::AutoSlowOperation;
using mozilla::CycleCollectedJSContext;
using mozilla::DOMEventTargetHelper;
@@ -137,7 +134,7 @@ void nsIGlobalObject::UnlinkObjectsInGlobal() {
}
}
- mReportRecords.Clear();
+ ClearReports();
mReportingObservers.Clear();
mCountQueuingStrategySizeFunction = nullptr;
mByteLengthQueuingStrategySizeFunction = nullptr;
@@ -154,7 +151,7 @@ void nsIGlobalObject::TraverseObjectsInGlobal(
}
nsIGlobalObject* tmp = this;
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportRecords)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportBuffer)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportingObservers)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCountQueuingStrategySizeFunction)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mByteLengthQueuingStrategySizeFunction)
@@ -389,7 +386,7 @@ void nsIGlobalObject::RegisterReportingObserver(ReportingObserver* aObserver,
return;
}
- for (Report* report : mReportRecords) {
+ for (const auto& report : mReportBuffer) {
aObserver->MaybeReport(report);
}
}
@@ -407,12 +404,24 @@ void nsIGlobalObject::BroadcastReport(Report* aReport) {
observer->MaybeReport(aReport);
}
- if (NS_WARN_IF(!mReportRecords.AppendElement(aReport, mozilla::fallible))) {
+ if (NS_WARN_IF(!mReportBuffer.AppendElement(aReport, mozilla::fallible))) {
return;
}
- while (mReportRecords.Length() > MAX_REPORT_RECORDS) {
- mReportRecords.RemoveElementAt(0);
+ uint32_t& count = mReportPerTypeCount.LookupOrInsert(aReport->Type());
+ ++count;
+
+ const uint32_t maxReportCount =
+ mozilla::StaticPrefs::dom_reporting_delivering_maxReports();
+ const nsString& reportType = aReport->Type();
+
+ for (size_t i = 0u; count > maxReportCount && i < mReportBuffer.Length();) {
+ if (mReportBuffer[i]->Type() == reportType) {
+ mReportBuffer.RemoveElementAt(i);
+ --count;
+ } else {
+ ++i;
+ }
}
}
@@ -428,7 +437,7 @@ void nsIGlobalObject::NotifyReportingObservers() {
}
void nsIGlobalObject::RemoveReportRecords() {
- mReportRecords.Clear();
+ ClearReports();
for (auto& observer : mReportingObservers) {
observer->ForgetReports();
@@ -518,3 +527,8 @@ void nsIGlobalObject::ReportToConsole(
nsContentUtils::ReportToConsole(aErrorFlags, aCategory, nullptr, aFile,
aMessageName.get(), aParams, aLocation);
}
+
+void nsIGlobalObject::ClearReports() {
+ mReportBuffer.Clear();
+ mReportPerTypeCount.Clear();
+}
diff --git a/dom/base/nsIGlobalObject.h b/dom/base/nsIGlobalObject.h
@@ -406,9 +406,14 @@ class nsIGlobalObject : public nsISupports {
size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aSizeOf) const;
private:
+ void ClearReports();
+
+ private:
// List of Report objects for ReportingObservers.
nsTArray<RefPtr<mozilla::dom::ReportingObserver>> mReportingObservers;
- nsTArray<RefPtr<mozilla::dom::Report>> mReportRecords;
+ // https://w3c.github.io/reporting/#windoworworkerglobalscope-report-buffer
+ nsTArray<RefPtr<mozilla::dom::Report>> mReportBuffer;
+ nsTHashMap<nsString, uint32_t> mReportPerTypeCount;
// https://streams.spec.whatwg.org/#count-queuing-strategy-size-function
RefPtr<mozilla::dom::Function> mCountQueuingStrategySizeFunction;
diff --git a/dom/reporting/Report.cpp b/dom/reporting/Report.cpp
@@ -39,6 +39,8 @@ JSObject* Report::WrapObject(JSContext* aCx,
return Report_Binding::Wrap(aCx, this, aGivenProto);
}
+const nsString& Report::Type() const { return mType; }
+
void Report::GetType(nsAString& aType) const { aType = mType; }
void Report::GetUrl(nsAString& aURL) const { aURL = mURL; }
diff --git a/dom/reporting/Report.h b/dom/reporting/Report.h
@@ -37,8 +37,9 @@ class Report final : public nsISupports, public nsWrapperCache {
nsIGlobalObject* GetParentObject() const { return mGlobal; }
- void GetType(nsAString& aType) const;
+ const nsString& Type() const;
+ void GetType(nsAString& aType) const;
void GetUrl(nsAString& aURL) const;
ReportBody* GetBody() const;
@@ -48,8 +49,8 @@ class Report final : public nsISupports, public nsWrapperCache {
nsCOMPtr<nsIGlobalObject> mGlobal;
- const nsString mType;
- const nsString mURL;
+ nsString mType;
+ nsString mURL;
RefPtr<ReportBody> mBody;
};
diff --git a/testing/web-platform/meta/reporting/reporting-api-honors-limits.https.sub.html.ini b/testing/web-platform/meta/reporting/reporting-api-honors-limits.https.sub.html.ini
@@ -1,14 +0,0 @@
-[reporting-api-honors-limits.https.sub.html]
- expected: TIMEOUT
-
- [CSP Report limits were honored]
- expected: TIMEOUT
- bug: 2003729
-
- [Combined report limits were honored]
- expected: NOTRUN
- bug: 2003729
-
- [Test Report limits were honored]
- expected: NOTRUN
- bug: 2003729
diff --git a/testing/web-platform/tests/reporting/reporting-api-orders-reports-buffered.https.html b/testing/web-platform/tests/reporting/reporting-api-orders-reports-buffered.https.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>
+ Reporting API: buffered observer preserves ordering across report types
+ </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+</head>
+
+<body>
+<script>
+promise_test(async t => {
+ assert_true("ReportingObserver" in self);
+
+ function generateTest(i) {
+ test_driver.generate_test_report(`Test report ${i}`);
+ }
+
+ function generateDeprecation(i) {
+ let img = document.createElement("img");
+
+ img.src = `/some/bunk/file${i}.png`;
+ document.body.appendChild(img);
+ }
+
+ let nonBufferedReports = [];
+ const observeTwoReports = async () => {
+ let { promise, resolve } = Promise.withResolvers();
+ let count = 0;
+ let reportingObserver = new ReportingObserver(reports => {
+ count += reports.length;
+ nonBufferedReports.push(...reports);
+ if (count >= 2) {
+ resolve();
+ }
+ });
+
+ reportingObserver.observe();
+ return promise.finally(() => reportingObserver.disconnect());
+ }
+
+ // Generate interleaved reports
+ for (let i = 0; i < 25; i++) {
+ generateTest(i);
+ generateDeprecation(i);
+ await observeTwoReports();
+ }
+
+ const collected = [];
+ const { promise: collectedAll, resolve } = Promise.withResolvers();
+
+ const observer = new ReportingObserver(
+ reports => {
+ collected.push(...reports);
+ if (collected.length == nonBufferedReports.length) {
+ resolve();
+ }
+ },
+ { buffered: true }
+ );
+
+ observer.observe();
+
+ await collectedAll;
+ observer.disconnect();
+
+ assert_greater_than(
+ collected.length,
+ 0,
+ "Some reports must be delivered"
+ );
+
+ // Verify relative ordering by type
+ const observedTypes = collected.map(r => r.type);
+
+ assert_array_equals(observedTypes, nonBufferedReports.map(r => r.type));
+}, "Buffered ReportingObserver preserves report generation order");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/reporting-api-orders-reports-buffered.https.html.sub.headers b/testing/web-platform/tests/reporting/reporting-api-orders-reports-buffered.https.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: reporting-api-sends-reports-on-violation={{$id:uuid()}}; Path=/content-security-policy/reporting-api
+Reporting-Endpoints: csp-group="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id}}"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group