Bug 1873144 - Disabled test_conformance__textures__misc__texture-npot-video.html...
[gecko.git] / dom / reporting / ReportDeliver.cpp
blob08a31e57ee2db351057b37da283b4bb0840700ce
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 "mozilla/JSONStringWriteFuncs.h"
8 #include "mozilla/StaticPrefs_dom.h"
9 #include "mozilla/dom/EndpointForReportChild.h"
10 #include "mozilla/dom/Fetch.h"
11 #include "mozilla/dom/Navigator.h"
12 #include "mozilla/dom/Promise.h"
13 #include "mozilla/dom/ReportBody.h"
14 #include "mozilla/dom/ReportDeliver.h"
15 #include "mozilla/dom/Request.h"
16 #include "mozilla/dom/RequestBinding.h"
17 #include "mozilla/dom/Response.h"
18 #include "mozilla/dom/RootedDictionary.h"
19 #include "mozilla/ipc/BackgroundChild.h"
20 #include "mozilla/ipc/PBackgroundChild.h"
21 #include "mozilla/ipc/PBackgroundSharedTypes.h"
22 #include "nsGlobalWindowInner.h"
23 #include "nsIGlobalObject.h"
24 #include "nsIXPConnect.h"
25 #include "nsNetUtil.h"
26 #include "nsStringStream.h"
28 namespace mozilla::dom {
30 namespace {
32 StaticRefPtr<ReportDeliver> gReportDeliver;
34 class ReportFetchHandler final : public PromiseNativeHandler {
35 public:
36 NS_DECL_ISUPPORTS
38 explicit ReportFetchHandler(
39 const nsTArray<ReportDeliver::ReportData>& aReportData)
40 : mReports(aReportData.Clone()) {}
42 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
43 ErrorResult& aRv) override {
44 if (!gReportDeliver) {
45 return;
48 if (NS_WARN_IF(!aValue.isObject())) {
49 return;
52 JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
53 MOZ_ASSERT(obj);
56 Response* response = nullptr;
57 if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Response, &obj, response)))) {
58 return;
61 if (response->Status() == 410) {
62 mozilla::ipc::PBackgroundChild* actorChild =
63 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
65 for (const auto& report : mReports) {
66 mozilla::ipc::PrincipalInfo principalInfo;
67 nsresult rv =
68 PrincipalToPrincipalInfo(report.mPrincipal, &principalInfo);
69 if (NS_WARN_IF(NS_FAILED(rv))) {
70 continue;
73 actorChild->SendRemoveEndpoint(report.mGroupName, report.mEndpointURL,
74 principalInfo);
80 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
81 ErrorResult& aRv) override {
82 if (gReportDeliver) {
83 for (auto& report : mReports) {
84 ++report.mFailures;
85 gReportDeliver->AppendReportData(report);
90 private:
91 ~ReportFetchHandler() = default;
93 nsTArray<ReportDeliver::ReportData> mReports;
96 NS_IMPL_ISUPPORTS0(ReportFetchHandler)
98 class ReportJSONWriter final : public JSONWriter {
99 public:
100 explicit ReportJSONWriter(JSONStringWriteFunc<nsAutoCString>& aOutput)
101 : JSONWriter(aOutput) {}
103 void JSONProperty(const Span<const char>& aProperty,
104 const Span<const char>& aJSON) {
105 Separator();
106 PropertyNameAndColon(aProperty);
107 mWriter.Write(aJSON);
111 void SendReports(nsTArray<ReportDeliver::ReportData>& aReports,
112 const nsCString& aEndPointUrl, nsIPrincipal* aPrincipal) {
113 if (NS_WARN_IF(aReports.IsEmpty())) {
114 return;
117 nsIXPConnect* xpc = nsContentUtils::XPConnect();
118 MOZ_ASSERT(xpc, "This should never be null!");
120 nsCOMPtr<nsIGlobalObject> globalObject;
122 AutoJSAPI jsapi;
123 jsapi.Init();
125 JSContext* cx = jsapi.cx();
126 JS::Rooted<JSObject*> sandbox(cx);
127 nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
128 if (NS_WARN_IF(NS_FAILED(rv))) {
129 return;
132 // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
133 // global.
134 MOZ_ASSERT(JS_IsGlobalObject(sandbox));
136 globalObject = xpc::NativeGlobal(sandbox);
139 if (NS_WARN_IF(!globalObject)) {
140 return;
143 // The body
144 JSONStringWriteFunc<nsAutoCString> body;
145 ReportJSONWriter w(body);
147 w.StartArrayElement();
148 for (const auto& report : aReports) {
149 MOZ_ASSERT(report.mPrincipal == aPrincipal);
150 MOZ_ASSERT(report.mEndpointURL == aEndPointUrl);
151 w.StartObjectElement();
152 w.IntProperty("age",
153 (TimeStamp::Now() - report.mCreationTime).ToMilliseconds());
154 w.StringProperty("type", NS_ConvertUTF16toUTF8(report.mType));
155 w.StringProperty("url", NS_ConvertUTF16toUTF8(report.mURL));
156 w.StringProperty("user_agent", NS_ConvertUTF16toUTF8(report.mUserAgent));
157 w.JSONProperty(MakeStringSpan("body"),
158 Span<const char>(report.mReportBodyJSON.Data(),
159 report.mReportBodyJSON.Length()));
160 w.EndObject();
162 w.EndArray();
164 // The body as stream
165 nsCOMPtr<nsIInputStream> streamBody;
166 nsresult rv =
167 NS_NewCStringInputStream(getter_AddRefs(streamBody), body.StringCRef());
169 // Headers
170 IgnoredErrorResult error;
171 RefPtr<InternalHeaders> internalHeaders =
172 new InternalHeaders(HeadersGuardEnum::Request);
173 internalHeaders->Set("Content-Type"_ns, "application/reports+json"_ns, error);
174 if (NS_WARN_IF(error.Failed())) {
175 return;
178 // URL and fragments
179 nsCOMPtr<nsIURI> uri;
180 rv = NS_NewURI(getter_AddRefs(uri), aEndPointUrl);
181 if (NS_WARN_IF(NS_FAILED(rv))) {
182 return;
185 nsCOMPtr<nsIURI> uriClone;
186 rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriClone));
187 if (NS_WARN_IF(NS_FAILED(rv))) {
188 return;
191 nsAutoCString uriSpec;
192 rv = uriClone->GetSpec(uriSpec);
193 if (NS_WARN_IF(NS_FAILED(rv))) {
194 return;
197 nsAutoCString uriFragment;
198 rv = uri->GetRef(uriFragment);
199 if (NS_WARN_IF(NS_FAILED(rv))) {
200 return;
203 auto internalRequest = MakeSafeRefPtr<InternalRequest>(uriSpec, uriFragment);
205 internalRequest->SetMethod("POST"_ns);
206 internalRequest->SetBody(streamBody, body.StringCRef().Length());
207 internalRequest->SetHeaders(internalHeaders);
208 internalRequest->SetSkipServiceWorker();
209 // TODO: internalRequest->SetContentPolicyType(TYPE_REPORT);
210 internalRequest->SetMode(RequestMode::Cors);
211 internalRequest->SetCredentialsMode(RequestCredentials::Include);
213 RefPtr<Request> request =
214 new Request(globalObject, std::move(internalRequest), nullptr);
216 RequestOrUSVString fetchInput;
217 fetchInput.SetAsRequest() = request;
219 RootedDictionary<RequestInit> requestInit(RootingCx());
220 RefPtr<Promise> promise = FetchRequest(globalObject, fetchInput, requestInit,
221 CallerType::NonSystem, error);
222 if (error.Failed()) {
223 for (auto& report : aReports) {
224 ++report.mFailures;
225 if (gReportDeliver) {
226 gReportDeliver->AppendReportData(report);
229 return;
232 RefPtr<ReportFetchHandler> handler = new ReportFetchHandler(aReports);
233 promise->AppendNativeHandler(handler);
236 } // namespace
238 /* static */
239 void ReportDeliver::Record(nsPIDOMWindowInner* aWindow, const nsAString& aType,
240 const nsAString& aGroupName, const nsAString& aURL,
241 ReportBody* aBody) {
242 MOZ_ASSERT(NS_IsMainThread());
243 MOZ_ASSERT(aWindow);
244 MOZ_ASSERT(aBody);
246 JSONStringWriteFunc<nsAutoCString> reportBodyJSON;
247 ReportJSONWriter w(reportBodyJSON);
249 w.Start();
250 aBody->ToJSON(w);
251 w.End();
253 nsCOMPtr<nsIPrincipal> principal =
254 nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
255 if (NS_WARN_IF(!principal)) {
256 return;
259 mozilla::ipc::PrincipalInfo principalInfo;
260 nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo);
261 if (NS_WARN_IF(NS_FAILED(rv))) {
262 return;
265 mozilla::ipc::PBackgroundChild* actorChild =
266 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
268 PEndpointForReportChild* actor =
269 actorChild->SendPEndpointForReportConstructor(nsString(aGroupName),
270 principalInfo);
271 if (NS_WARN_IF(!actor)) {
272 return;
275 ReportData data;
276 data.mType = aType;
277 data.mGroupName = aGroupName;
278 data.mURL = aURL;
279 data.mCreationTime = TimeStamp::Now();
280 data.mReportBodyJSON = std::move(reportBodyJSON).StringRRef();
281 data.mPrincipal = principal;
282 data.mFailures = 0;
284 Navigator* navigator = aWindow->Navigator();
285 MOZ_ASSERT(navigator);
287 IgnoredErrorResult error;
288 navigator->GetUserAgent(data.mUserAgent, CallerType::NonSystem, error);
289 if (NS_WARN_IF(error.Failed())) {
290 return;
293 static_cast<EndpointForReportChild*>(actor)->Initialize(data);
296 /* static */
297 void ReportDeliver::Fetch(const ReportData& aReportData) {
298 if (!gReportDeliver) {
299 RefPtr<ReportDeliver> rd = new ReportDeliver();
301 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
302 if (NS_WARN_IF(!obs)) {
303 return;
306 obs->AddObserver(rd, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
307 gReportDeliver = rd;
310 gReportDeliver->AppendReportData(aReportData);
313 void ReportDeliver::AppendReportData(const ReportData& aReportData) {
314 if (aReportData.mFailures >
315 StaticPrefs::dom_reporting_delivering_maxFailures()) {
316 return;
319 if (NS_WARN_IF(!mReportQueue.AppendElement(aReportData, fallible))) {
320 return;
323 while (mReportQueue.Length() >
324 StaticPrefs::dom_reporting_delivering_maxReports()) {
325 mReportQueue.RemoveElementAt(0);
328 if (!mTimer) {
329 uint32_t timeout = StaticPrefs::dom_reporting_delivering_timeout() * 1000;
330 nsresult rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, timeout,
331 nsITimer::TYPE_ONE_SHOT);
332 Unused << NS_WARN_IF(NS_FAILED(rv));
336 NS_IMETHODIMP
337 ReportDeliver::Notify(nsITimer* aTimer) {
338 mTimer = nullptr;
340 nsTArray<ReportData> reports = std::move(mReportQueue);
342 // group reports by endpoint and nsIPrincipal
343 std::map<std::pair<nsCString, nsCOMPtr<nsIPrincipal>>, nsTArray<ReportData>>
344 reportsByPrincipal;
345 for (ReportData& report : reports) {
346 auto already_seen =
347 reportsByPrincipal.find({report.mEndpointURL, report.mPrincipal});
348 if (already_seen == reportsByPrincipal.end()) {
349 reportsByPrincipal.emplace(
350 std::make_pair(report.mEndpointURL, report.mPrincipal),
351 nsTArray<ReportData>({report}));
352 } else {
353 already_seen->second.AppendElement(report);
357 for (auto& iter : reportsByPrincipal) {
358 std::pair<nsCString, nsCOMPtr<nsIPrincipal>> key = iter.first;
359 nsTArray<ReportData>& value = iter.second;
360 nsCString url = key.first;
361 nsCOMPtr<nsIPrincipal> principal = key.second;
362 nsAutoCString u(url);
363 SendReports(value, url, principal);
366 return NS_OK;
369 NS_IMETHODIMP
370 ReportDeliver::GetName(nsACString& aName) {
371 aName.AssignLiteral("ReportDeliver");
372 return NS_OK;
375 NS_IMETHODIMP
376 ReportDeliver::Observe(nsISupports* aSubject, const char* aTopic,
377 const char16_t* aData) {
378 MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
380 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
381 if (NS_WARN_IF(!obs)) {
382 return NS_OK;
385 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
387 if (mTimer) {
388 mTimer->Cancel();
389 mTimer = nullptr;
392 gReportDeliver = nullptr;
393 return NS_OK;
396 ReportDeliver::ReportDeliver() = default;
398 ReportDeliver::~ReportDeliver() = default;
400 NS_INTERFACE_MAP_BEGIN(ReportDeliver)
401 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
402 NS_INTERFACE_MAP_ENTRY(nsIObserver)
403 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
404 NS_INTERFACE_MAP_ENTRY(nsINamed)
405 NS_INTERFACE_MAP_END
407 NS_IMPL_ADDREF(ReportDeliver)
408 NS_IMPL_RELEASE(ReportDeliver)
410 } // namespace mozilla::dom