tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mdom/base/nsIGlobalObject.cpp | 34++++++++++++++++++++++++----------
Mdom/base/nsIGlobalObject.h | 7++++++-
Mdom/reporting/Report.cpp | 2++
Mdom/reporting/Report.h | 7++++---
Dtesting/web-platform/meta/reporting/reporting-api-honors-limits.https.sub.html.ini | 14--------------
Atesting/web-platform/tests/reporting/reporting-api-orders-reports-buffered.https.html | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/reporting/reporting-api-orders-reports-buffered.https.html.sub.headers | 7+++++++
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