Bug 1472338: part 2) Change `clipboard.readText()` to read from the clipboard asynchr...
[gecko.git] / dom / reporting / ReportDeliver.cpp
blobce9463450f5be99f64d71f239e76b20292e79e35
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/JSONWriter.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/ipc/BackgroundChild.h"
19 #include "mozilla/ipc/PBackgroundChild.h"
20 #include "mozilla/ipc/PBackgroundSharedTypes.h"
21 #include "nsGlobalWindowInner.h"
22 #include "nsIGlobalObject.h"
23 #include "nsIXPConnect.h"
24 #include "nsNetUtil.h"
25 #include "nsStringStream.h"
27 namespace mozilla {
28 namespace 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 struct StringWriteFunc final : public JSONWriteFunc {
99 nsACString&
100 mBuffer; // The lifetime of the struct must be bound to the buffer
101 explicit StringWriteFunc(nsACString& aBuffer) : mBuffer(aBuffer) {}
103 void Write(const Span<const char>& aStr) override { mBuffer.Append(aStr); }
106 class ReportJSONWriter final : public JSONWriter {
107 public:
108 explicit ReportJSONWriter(nsACString& aOutput)
109 : JSONWriter(MakeUnique<StringWriteFunc>(aOutput)) {}
111 void JSONProperty(const Span<const char>& aProperty,
112 const Span<const char>& aJSON) {
113 Separator();
114 PropertyNameAndColon(aProperty);
115 mWriter->Write(aJSON);
119 void SendReports(nsTArray<ReportDeliver::ReportData>& aReports,
120 const nsCString& aEndPointUrl, nsIPrincipal* aPrincipal) {
121 if (NS_WARN_IF(aReports.IsEmpty())) {
122 return;
125 nsIXPConnect* xpc = nsContentUtils::XPConnect();
126 MOZ_ASSERT(xpc, "This should never be null!");
128 nsCOMPtr<nsIGlobalObject> globalObject;
130 AutoJSAPI jsapi;
131 jsapi.Init();
133 JSContext* cx = jsapi.cx();
134 JS::Rooted<JSObject*> sandbox(cx);
135 nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
136 if (NS_WARN_IF(NS_FAILED(rv))) {
137 return;
140 // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
141 // global.
142 MOZ_ASSERT(JS_IsGlobalObject(sandbox));
144 globalObject = xpc::NativeGlobal(sandbox);
147 if (NS_WARN_IF(!globalObject)) {
148 return;
151 // The body
152 nsAutoCString body;
153 ReportJSONWriter w(body);
155 w.StartArrayElement();
156 for (const auto& report : aReports) {
157 MOZ_ASSERT(report.mPrincipal == aPrincipal);
158 MOZ_ASSERT(report.mEndpointURL == aEndPointUrl);
159 w.StartObjectElement();
160 w.IntProperty("age",
161 (TimeStamp::Now() - report.mCreationTime).ToMilliseconds());
162 w.StringProperty("type", NS_ConvertUTF16toUTF8(report.mType));
163 w.StringProperty("url", NS_ConvertUTF16toUTF8(report.mURL));
164 w.StringProperty("user_agent", NS_ConvertUTF16toUTF8(report.mUserAgent));
165 w.JSONProperty(MakeStringSpan("body"),
166 Span<const char>(report.mReportBodyJSON.Data(),
167 report.mReportBodyJSON.Length()));
168 w.EndObject();
170 w.EndArray();
172 // The body as stream
173 nsCOMPtr<nsIInputStream> streamBody;
174 nsresult rv = NS_NewCStringInputStream(getter_AddRefs(streamBody), body);
176 // Headers
177 IgnoredErrorResult error;
178 RefPtr<InternalHeaders> internalHeaders =
179 new InternalHeaders(HeadersGuardEnum::Request);
180 internalHeaders->Set("Content-Type"_ns, "application/reports+json"_ns, error);
181 if (NS_WARN_IF(error.Failed())) {
182 return;
185 // URL and fragments
186 nsCOMPtr<nsIURI> uri;
187 rv = NS_NewURI(getter_AddRefs(uri), aEndPointUrl);
188 if (NS_WARN_IF(NS_FAILED(rv))) {
189 return;
192 nsCOMPtr<nsIURI> uriClone;
193 rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriClone));
194 if (NS_WARN_IF(NS_FAILED(rv))) {
195 return;
198 nsAutoCString uriSpec;
199 rv = uriClone->GetSpec(uriSpec);
200 if (NS_WARN_IF(NS_FAILED(rv))) {
201 return;
204 nsAutoCString uriFragment;
205 rv = uri->GetRef(uriFragment);
206 if (NS_WARN_IF(NS_FAILED(rv))) {
207 return;
210 auto internalRequest = MakeSafeRefPtr<InternalRequest>(uriSpec, uriFragment);
212 internalRequest->SetMethod("POST"_ns);
213 internalRequest->SetBody(streamBody, body.Length());
214 internalRequest->SetHeaders(internalHeaders);
215 internalRequest->SetSkipServiceWorker();
216 // TODO: internalRequest->SetContentPolicyType(TYPE_REPORT);
217 internalRequest->SetMode(RequestMode::Cors);
218 internalRequest->SetCredentialsMode(RequestCredentials::Include);
220 RefPtr<Request> request =
221 new Request(globalObject, std::move(internalRequest), nullptr);
223 RequestOrUSVString fetchInput;
224 fetchInput.SetAsRequest() = request;
226 RootedDictionary<RequestInit> requestInit(RootingCx());
227 RefPtr<Promise> promise = FetchRequest(globalObject, fetchInput, requestInit,
228 CallerType::NonSystem, error);
229 if (error.Failed()) {
230 for (auto& report : aReports) {
231 ++report.mFailures;
232 if (gReportDeliver) {
233 gReportDeliver->AppendReportData(report);
236 return;
239 RefPtr<ReportFetchHandler> handler = new ReportFetchHandler(aReports);
240 promise->AppendNativeHandler(handler);
243 } // namespace
245 /* static */
246 void ReportDeliver::Record(nsPIDOMWindowInner* aWindow, const nsAString& aType,
247 const nsAString& aGroupName, const nsAString& aURL,
248 ReportBody* aBody) {
249 MOZ_ASSERT(NS_IsMainThread());
250 MOZ_ASSERT(aWindow);
251 MOZ_ASSERT(aBody);
253 nsAutoCString reportBodyJSON;
254 ReportJSONWriter w(reportBodyJSON);
256 w.Start();
257 aBody->ToJSON(w);
258 w.End();
260 nsCOMPtr<nsIPrincipal> principal =
261 nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
262 if (NS_WARN_IF(!principal)) {
263 return;
266 mozilla::ipc::PrincipalInfo principalInfo;
267 nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo);
268 if (NS_WARN_IF(NS_FAILED(rv))) {
269 return;
272 mozilla::ipc::PBackgroundChild* actorChild =
273 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
275 PEndpointForReportChild* actor =
276 actorChild->SendPEndpointForReportConstructor(nsString(aGroupName),
277 principalInfo);
278 if (NS_WARN_IF(!actor)) {
279 return;
282 ReportData data;
283 data.mType = aType;
284 data.mGroupName = aGroupName;
285 data.mURL = aURL;
286 data.mCreationTime = TimeStamp::Now();
287 data.mReportBodyJSON = reportBodyJSON;
288 data.mPrincipal = principal;
289 data.mFailures = 0;
291 Navigator* navigator = aWindow->Navigator();
292 MOZ_ASSERT(navigator);
294 IgnoredErrorResult error;
295 navigator->GetUserAgent(data.mUserAgent, CallerType::NonSystem, error);
296 if (NS_WARN_IF(error.Failed())) {
297 return;
300 static_cast<EndpointForReportChild*>(actor)->Initialize(data);
303 /* static */
304 void ReportDeliver::Fetch(const ReportData& aReportData) {
305 if (!gReportDeliver) {
306 RefPtr<ReportDeliver> rd = new ReportDeliver();
308 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
309 if (NS_WARN_IF(!obs)) {
310 return;
313 obs->AddObserver(rd, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
314 gReportDeliver = rd;
317 gReportDeliver->AppendReportData(aReportData);
320 void ReportDeliver::AppendReportData(const ReportData& aReportData) {
321 if (aReportData.mFailures >
322 StaticPrefs::dom_reporting_delivering_maxFailures()) {
323 return;
326 if (NS_WARN_IF(!mReportQueue.AppendElement(aReportData, fallible))) {
327 return;
330 while (mReportQueue.Length() >
331 StaticPrefs::dom_reporting_delivering_maxReports()) {
332 mReportQueue.RemoveElementAt(0);
335 if (!mTimer) {
336 uint32_t timeout = StaticPrefs::dom_reporting_delivering_timeout() * 1000;
337 nsresult rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, timeout,
338 nsITimer::TYPE_ONE_SHOT);
339 Unused << NS_WARN_IF(NS_FAILED(rv));
343 NS_IMETHODIMP
344 ReportDeliver::Notify(nsITimer* aTimer) {
345 mTimer = nullptr;
347 nsTArray<ReportData> reports = std::move(mReportQueue);
349 // group reports by endpoint and nsIPrincipal
350 std::map<std::pair<nsCString, nsCOMPtr<nsIPrincipal>>, nsTArray<ReportData>>
351 reportsByPrincipal;
352 for (ReportData& report : reports) {
353 auto already_seen =
354 reportsByPrincipal.find({report.mEndpointURL, report.mPrincipal});
355 if (already_seen == reportsByPrincipal.end()) {
356 reportsByPrincipal.emplace(
357 std::make_pair(report.mEndpointURL, report.mPrincipal),
358 nsTArray<ReportData>({report}));
359 } else {
360 already_seen->second.AppendElement(report);
364 for (auto& iter : reportsByPrincipal) {
365 std::pair<nsCString, nsCOMPtr<nsIPrincipal>> key = iter.first;
366 nsTArray<ReportData>& value = iter.second;
367 nsCString url = key.first;
368 nsCOMPtr<nsIPrincipal> principal = key.second;
369 nsAutoCString u(url);
370 SendReports(value, url, principal);
373 return NS_OK;
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 if (mTimer) {
395 mTimer->Cancel();
396 mTimer = nullptr;
399 gReportDeliver = nullptr;
400 return NS_OK;
403 ReportDeliver::ReportDeliver() = default;
405 ReportDeliver::~ReportDeliver() = default;
407 NS_INTERFACE_MAP_BEGIN(ReportDeliver)
408 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
409 NS_INTERFACE_MAP_ENTRY(nsIObserver)
410 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
411 NS_INTERFACE_MAP_ENTRY(nsINamed)
412 NS_INTERFACE_MAP_END
414 NS_IMPL_ADDREF(ReportDeliver)
415 NS_IMPL_RELEASE(ReportDeliver)
417 } // namespace dom
418 } // namespace mozilla