Backed out 2 changesets (bug 1926630) for causing macOS assertion failures. CLOSED...
[gecko.git] / dom / reporting / ReportDeliver.cpp
blob34de7b77425db0f49a18277caf24f2dd97c48122
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <algorithm>
9 #include "mozilla/JSONStringWriteFuncs.h"
10 #include "mozilla/StaticPrefs_dom.h"
11 #include "mozilla/dom/EndpointForReportChild.h"
12 #include "mozilla/dom/Fetch.h"
13 #include "mozilla/dom/Navigator.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/ReportBody.h"
16 #include "mozilla/dom/ReportDeliver.h"
17 #include "mozilla/dom/Request.h"
18 #include "mozilla/dom/RequestBinding.h"
19 #include "mozilla/dom/Response.h"
20 #include "mozilla/dom/RootedDictionary.h"
21 #include "mozilla/ipc/BackgroundChild.h"
22 #include "mozilla/ipc/PBackgroundChild.h"
23 #include "mozilla/ipc/PBackgroundSharedTypes.h"
24 #include "nsGlobalWindowInner.h"
25 #include "nsIGlobalObject.h"
26 #include "nsIXPConnect.h"
27 #include "nsNetUtil.h"
28 #include "nsStringStream.h"
30 namespace mozilla::dom {
32 namespace {
34 StaticRefPtr<ReportDeliver> gReportDeliver;
36 // This is the same value as the default value of
37 // dom.min_timeout_value, so it's not that random.
38 constexpr double gMinReportAgeInMs = 4.0;
40 class ReportFetchHandler final : public PromiseNativeHandler {
41 public:
42 NS_DECL_ISUPPORTS
44 explicit ReportFetchHandler(
45 const nsTArray<ReportDeliver::ReportData>& aReportData)
46 : mReports(aReportData.Clone()) {}
48 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
49 ErrorResult& aRv) override {
50 if (!gReportDeliver) {
51 return;
54 if (NS_WARN_IF(!aValue.isObject())) {
55 return;
58 JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
59 MOZ_ASSERT(obj);
62 Response* response = nullptr;
63 if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Response, &obj, response)))) {
64 return;
67 if (response->Status() == 410) {
68 mozilla::ipc::PBackgroundChild* actorChild =
69 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
71 for (const auto& report : mReports) {
72 mozilla::ipc::PrincipalInfo principalInfo;
73 nsresult rv =
74 PrincipalToPrincipalInfo(report.mPrincipal, &principalInfo);
75 if (NS_WARN_IF(NS_FAILED(rv))) {
76 continue;
79 actorChild->SendRemoveEndpoint(report.mGroupName, report.mEndpointURL,
80 principalInfo);
86 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
87 ErrorResult& aRv) override {
88 if (gReportDeliver) {
89 for (auto& report : mReports) {
90 ++report.mFailures;
91 gReportDeliver->AppendReportData(report);
96 private:
97 ~ReportFetchHandler() = default;
99 nsTArray<ReportDeliver::ReportData> mReports;
102 NS_IMPL_ISUPPORTS0(ReportFetchHandler)
104 class ReportJSONWriter final : public JSONWriter {
105 public:
106 explicit ReportJSONWriter(JSONStringWriteFunc<nsAutoCString>& aOutput)
107 : JSONWriter(aOutput) {}
109 void JSONProperty(const Span<const char>& aProperty,
110 const Span<const char>& aJSON) {
111 Separator();
112 PropertyNameAndColon(aProperty);
113 mWriter.Write(aJSON);
117 void SendReports(nsTArray<ReportDeliver::ReportData>& aReports,
118 const nsCString& aEndPointUrl, nsIPrincipal* aPrincipal) {
119 if (NS_WARN_IF(aReports.IsEmpty())) {
120 return;
123 nsIXPConnect* xpc = nsContentUtils::XPConnect();
124 MOZ_ASSERT(xpc, "This should never be null!");
126 nsCOMPtr<nsIGlobalObject> globalObject;
128 AutoJSAPI jsapi;
129 jsapi.Init();
131 JSContext* cx = jsapi.cx();
132 JS::Rooted<JSObject*> sandbox(cx);
133 nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
134 if (NS_WARN_IF(NS_FAILED(rv))) {
135 return;
138 // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
139 // global.
140 MOZ_ASSERT(JS_IsGlobalObject(sandbox));
142 globalObject = xpc::NativeGlobal(sandbox);
145 if (NS_WARN_IF(!globalObject)) {
146 return;
149 // The body
150 JSONStringWriteFunc<nsAutoCString> body;
151 ReportJSONWriter w(body);
153 w.StartArrayElement();
154 for (const auto& report : aReports) {
155 MOZ_ASSERT(report.mPrincipal == aPrincipal);
156 MOZ_ASSERT(report.mEndpointURL == aEndPointUrl);
157 w.StartObjectElement();
158 // It looks like in rare cases, TimeStamp::Now() may be the same
159 // as report.mCreationTime, so we introduce a constant number to
160 // make sure "age" is always not 0.
161 w.IntProperty(
162 "age",
163 std::max((TimeStamp::Now() - report.mCreationTime).ToMilliseconds(),
164 gMinReportAgeInMs));
165 w.StringProperty("type", NS_ConvertUTF16toUTF8(report.mType));
166 w.StringProperty("url", NS_ConvertUTF16toUTF8(report.mURL));
167 w.StringProperty("user_agent", NS_ConvertUTF16toUTF8(report.mUserAgent));
168 w.JSONProperty(MakeStringSpan("body"),
169 Span<const char>(report.mReportBodyJSON.Data(),
170 report.mReportBodyJSON.Length()));
171 w.EndObject();
173 w.EndArray();
175 // The body as stream
176 nsCOMPtr<nsIInputStream> streamBody;
177 nsresult rv =
178 NS_NewCStringInputStream(getter_AddRefs(streamBody), body.StringCRef());
180 // Headers
181 IgnoredErrorResult error;
182 RefPtr<InternalHeaders> internalHeaders =
183 new InternalHeaders(HeadersGuardEnum::Request);
184 internalHeaders->Set("Content-Type"_ns, "application/reports+json"_ns, error);
185 if (NS_WARN_IF(error.Failed())) {
186 return;
189 // URL and fragments
190 nsCOMPtr<nsIURI> uri;
191 rv = NS_NewURI(getter_AddRefs(uri), aEndPointUrl);
192 if (NS_WARN_IF(NS_FAILED(rv))) {
193 return;
196 nsCOMPtr<nsIURI> uriClone;
197 rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriClone));
198 if (NS_WARN_IF(NS_FAILED(rv))) {
199 return;
202 nsAutoCString uriSpec;
203 rv = uriClone->GetSpec(uriSpec);
204 if (NS_WARN_IF(NS_FAILED(rv))) {
205 return;
208 nsAutoCString uriFragment;
209 rv = uri->GetRef(uriFragment);
210 if (NS_WARN_IF(NS_FAILED(rv))) {
211 return;
214 auto internalRequest = MakeSafeRefPtr<InternalRequest>(uriSpec, uriFragment);
216 internalRequest->SetMethod("POST"_ns);
217 internalRequest->SetBody(streamBody, body.StringCRef().Length());
218 internalRequest->SetHeaders(internalHeaders);
219 internalRequest->SetSkipServiceWorker();
220 // TODO: internalRequest->SetContentPolicyType(TYPE_REPORT);
221 internalRequest->SetMode(RequestMode::Cors);
222 internalRequest->SetCredentialsMode(RequestCredentials::Same_origin);
224 RefPtr<Request> request =
225 new Request(globalObject, std::move(internalRequest), nullptr);
227 RequestOrUTF8String fetchInput;
228 fetchInput.SetAsRequest() = request;
230 RootedDictionary<RequestInit> requestInit(RootingCx());
231 RefPtr<Promise> promise = FetchRequest(globalObject, fetchInput, requestInit,
232 CallerType::NonSystem, error);
233 if (error.Failed()) {
234 for (auto& report : aReports) {
235 ++report.mFailures;
236 if (gReportDeliver) {
237 gReportDeliver->AppendReportData(report);
240 return;
243 RefPtr<ReportFetchHandler> handler = new ReportFetchHandler(aReports);
244 promise->AppendNativeHandler(handler);
247 } // namespace
249 /* static */
250 void ReportDeliver::Record(nsPIDOMWindowInner* aWindow, const nsAString& aType,
251 const nsAString& aGroupName, const nsAString& aURL,
252 ReportBody* aBody) {
253 MOZ_ASSERT(NS_IsMainThread());
254 MOZ_ASSERT(aWindow);
255 MOZ_ASSERT(aBody);
257 JSONStringWriteFunc<nsAutoCString> reportBodyJSON;
258 ReportJSONWriter w(reportBodyJSON);
260 w.Start();
261 aBody->ToJSON(w);
262 w.End();
264 nsCOMPtr<nsIPrincipal> principal =
265 nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
266 if (NS_WARN_IF(!principal)) {
267 return;
270 mozilla::ipc::PrincipalInfo principalInfo;
271 nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo);
272 if (NS_WARN_IF(NS_FAILED(rv))) {
273 return;
276 mozilla::ipc::PBackgroundChild* actorChild =
277 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
279 PEndpointForReportChild* actor =
280 actorChild->SendPEndpointForReportConstructor(nsString(aGroupName),
281 principalInfo);
282 if (NS_WARN_IF(!actor)) {
283 return;
286 ReportData data;
287 data.mType = aType;
288 data.mGroupName = aGroupName;
289 data.mURL = aURL;
290 data.mCreationTime = TimeStamp::Now();
291 data.mReportBodyJSON = std::move(reportBodyJSON).StringRRef();
292 data.mPrincipal = principal;
293 data.mFailures = 0;
295 Navigator* navigator = aWindow->Navigator();
296 MOZ_ASSERT(navigator);
298 IgnoredErrorResult error;
299 navigator->GetUserAgent(data.mUserAgent, CallerType::NonSystem, error);
300 if (NS_WARN_IF(error.Failed())) {
301 return;
304 static_cast<EndpointForReportChild*>(actor)->Initialize(data);
307 /* static */
308 void ReportDeliver::Fetch(const ReportData& aReportData) {
309 if (!gReportDeliver) {
310 RefPtr<ReportDeliver> rd = new ReportDeliver();
312 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
313 if (NS_WARN_IF(!obs)) {
314 return;
317 obs->AddObserver(rd, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
318 gReportDeliver = rd;
321 gReportDeliver->AppendReportData(aReportData);
324 void ReportDeliver::AppendReportData(const ReportData& aReportData) {
325 if (aReportData.mFailures >
326 StaticPrefs::dom_reporting_delivering_maxFailures()) {
327 return;
330 if (NS_WARN_IF(!mReportQueue.AppendElement(aReportData, fallible))) {
331 return;
334 while (mReportQueue.Length() >
335 StaticPrefs::dom_reporting_delivering_maxReports()) {
336 mReportQueue.RemoveElementAt(0);
339 RefPtr<ReportDeliver> self{this};
340 nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
341 "ReportDeliver::CallNotify", [self]() { self->Notify(); });
343 NS_DispatchToCurrentThreadQueue(
344 runnable.forget(), StaticPrefs::dom_reporting_delivering_timeout() * 1000,
345 EventQueuePriority::Idle);
348 void ReportDeliver::Notify() {
349 nsTArray<ReportData> reports = std::move(mReportQueue);
351 // group reports by endpoint and nsIPrincipal
352 std::map<std::pair<nsCString, nsCOMPtr<nsIPrincipal>>, nsTArray<ReportData>>
353 reportsByPrincipal;
354 for (ReportData& report : reports) {
355 auto already_seen =
356 reportsByPrincipal.find({report.mEndpointURL, report.mPrincipal});
357 if (already_seen == reportsByPrincipal.end()) {
358 reportsByPrincipal.emplace(
359 std::make_pair(report.mEndpointURL, report.mPrincipal),
360 nsTArray<ReportData>({report}));
361 } else {
362 already_seen->second.AppendElement(report);
366 for (auto& iter : reportsByPrincipal) {
367 std::pair<nsCString, nsCOMPtr<nsIPrincipal>> key = iter.first;
368 nsTArray<ReportData>& value = iter.second;
369 nsCString url = key.first;
370 nsCOMPtr<nsIPrincipal> principal = key.second;
371 nsAutoCString u(url);
372 SendReports(value, url, principal);
376 NS_IMETHODIMP
377 ReportDeliver::GetName(nsACString& aName) {
378 aName.AssignLiteral("ReportDeliver");
379 return NS_OK;
382 NS_IMETHODIMP
383 ReportDeliver::Observe(nsISupports* aSubject, const char* aTopic,
384 const char16_t* aData) {
385 MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
387 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
388 if (NS_WARN_IF(!obs)) {
389 return NS_OK;
392 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
394 gReportDeliver = nullptr;
395 return NS_OK;
398 ReportDeliver::ReportDeliver() = default;
400 ReportDeliver::~ReportDeliver() = default;
402 NS_INTERFACE_MAP_BEGIN(ReportDeliver)
403 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
404 NS_INTERFACE_MAP_ENTRY(nsIObserver)
405 NS_INTERFACE_MAP_ENTRY(nsINamed)
406 NS_INTERFACE_MAP_END
408 NS_IMPL_ADDREF(ReportDeliver)
409 NS_IMPL_RELEASE(ReportDeliver)
411 } // namespace mozilla::dom