Bug 1607364 - CrashReporting API r=baku

Implement Crash Report for Reporting API.

Differential Revision: https://phabricator.services.mozilla.com/D58053

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Arnaud Renevier 2020-01-21 19:31:26 +00:00
Родитель aa9880c96a
Коммит 18693a1c1f
14 изменённых файлов: 290 добавлений и 47 удалений

Просмотреть файл

@ -100,6 +100,7 @@
#include "gfxUtils.h"
#include "nsILoginManagerAuthPrompter.h"
#include "nsPIWindowRoot.h"
#include "nsReadableUtils.h"
#include "nsIAuthPrompt2.h"
#include "gfxDrawable.h"
#include "ImageOps.h"
@ -114,6 +115,7 @@
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "MMPrinter.h"
#include "SessionStoreFunctions.h"
#include "mozilla/dom/CrashReport.h"
#ifdef XP_WIN
# include "mozilla/plugins/PluginWidgetParent.h"
@ -705,6 +707,32 @@ void BrowserParent::ActorDestroy(ActorDestroyReason why) {
// case of a crash.
BrowserParent::PopFocus(this);
if (why == AbnormalShutdown) {
// dom_reporting_header must also be enabled for the report to be sent.
if (StaticPrefs::dom_reporting_crash_enabled()) {
nsCOMPtr<nsIPrincipal> principal = GetContentPrincipal();
if (principal) {
nsAutoCString crash_reason;
CrashReporter::GetAnnotation(OtherPid(),
CrashReporter::Annotation::MozCrashReason,
crash_reason);
// FIXME(arenevier): Find a less fragile way to identify that a crash
// was caused by OOM
bool is_oom = false;
if (crash_reason == "OOM" || crash_reason == "OOM!" ||
StringBeginsWith(crash_reason,
NS_LITERAL_CSTRING("[unhandlable oom]")) ||
StringBeginsWith(crash_reason,
NS_LITERAL_CSTRING("Unhandlable OOM"))) {
is_oom = true;
}
CrashReport::Deliver(principal, is_oom);
}
}
}
// Prevent executing ContentParent::NotifyTabDestroying in
// BrowserParent::Destroy() called by frameLoader->DestroyComplete() below
// when tab crashes in contentprocess because ContentParent::ActorDestroy()

Просмотреть файл

@ -0,0 +1,79 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/CrashReport.h"
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/ReportingHeader.h"
#include "mozilla/dom/ReportDeliver.h"
#include "nsIPrincipal.h"
#include "nsIURIMutator.h"
#include "nsString.h"
namespace mozilla {
namespace dom {
struct StringWriteFunc : public JSONWriteFunc {
nsCString& mCString;
explicit StringWriteFunc(nsCString& aCString) : mCString(aCString) {}
void Write(const char* aStr) override { mCString.Append(aStr); }
};
/* static */
bool CrashReport::Deliver(nsIPrincipal* aPrincipal, bool aIsOOM) {
MOZ_ASSERT(aPrincipal);
nsAutoCString endpoint_url;
ReportingHeader::GetEndpointForReport(NS_LITERAL_STRING("default"),
aPrincipal, endpoint_url);
if (endpoint_url.IsEmpty()) {
return false;
}
nsCOMPtr<nsIURI> origin_uri;
nsresult rv = aPrincipal->GetURI(getter_AddRefs(origin_uri));
NS_ENSURE_SUCCESS(rv, false);
NS_ENSURE_TRUE(origin_uri, false);
nsCOMPtr<nsIURI> safe_origin_uri;
rv = NS_MutateURI(origin_uri)
.SetUserPass(EmptyCString())
.Finalize(safe_origin_uri);
NS_ENSURE_SUCCESS(rv, false);
NS_ENSURE_TRUE(safe_origin_uri, false);
nsAutoCString safe_origin_spec;
rv = safe_origin_uri->GetSpec(safe_origin_spec);
NS_ENSURE_SUCCESS(rv, false);
ReportDeliver::ReportData data;
data.mType = NS_LITERAL_STRING("crash");
data.mGroupName = NS_LITERAL_STRING("default");
data.mURL = NS_ConvertUTF8toUTF16(safe_origin_spec);
data.mCreationTime = TimeStamp::Now();
Navigator::GetUserAgent(nullptr, aPrincipal, false, data.mUserAgent);
data.mPrincipal = aPrincipal;
data.mFailures = 0;
data.mEndpointURL = endpoint_url;
nsCString body;
JSONWriter writer{MakeUnique<StringWriteFunc>(body)};
writer.Start();
if (aIsOOM) {
writer.StringProperty("reason", "oom");
}
writer.End();
data.mReportBodyJSON = body;
ReportDeliver::Fetch(data);
return true;
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,25 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_CrashReport_h
#define mozilla_dom_CrashReport_h
#include "nsCOMPtr.h"
class nsIPrincipal;
namespace mozilla {
namespace dom {
class CrashReport {
public:
static bool Deliver(nsIPrincipal* aPrincipal, bool aIsOOM);
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_CrashReport_h

Просмотреть файл

@ -16,6 +16,8 @@ class nsPIDOMWindowInner;
namespace mozilla {
namespace dom {
class ReportBody;
class ReportDeliver final : public nsIObserver, public nsITimerCallback {
public:
NS_DECL_ISUPPORTS

Просмотреть файл

@ -481,19 +481,25 @@ void ReportingHeader::GetEndpointForReport(
const nsAString& aGroupName,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
nsACString& aEndpointURI) {
nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(aPrincipalInfo);
if (NS_WARN_IF(!principal)) {
return;
}
GetEndpointForReport(aGroupName, principal, aEndpointURI);
}
/* static */
void ReportingHeader::GetEndpointForReport(const nsAString& aGroupName,
nsIPrincipal* aPrincipal,
nsACString& aEndpointURI) {
MOZ_ASSERT(aEndpointURI.IsEmpty());
if (!gReporting) {
return;
}
nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(aPrincipalInfo);
if (NS_WARN_IF(!principal)) {
return;
}
nsAutoCString origin;
nsresult rv = principal->GetOrigin(origin);
nsresult rv = aPrincipal->GetOrigin(origin);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}

Просмотреть файл

@ -63,6 +63,10 @@ class ReportingHeader final : public nsIObserver, public nsITimerCallback {
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
nsACString& aEndpointURI);
static void GetEndpointForReport(const nsAString& aGroupName,
nsIPrincipal* aPrincipal,
nsACString& aEndpointURI);
static void RemoveEndpoint(const nsAString& aGroupName,
const nsACString& aEndpointURL,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo);

Просмотреть файл

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS.mozilla.dom = [
'CrashReport.h',
'DeprecationReportBody.h',
'EndpointForReportChild.h',
'EndpointForReportParent.h',
@ -19,6 +20,7 @@ EXPORTS.mozilla.dom = [
]
UNIFIED_SOURCES += [
'CrashReport.cpp',
'DeprecationReportBody.cpp',
'EndpointForReportChild.cpp',
'EndpointForReportParent.cpp',

Просмотреть файл

@ -1,6 +1,17 @@
[DEFAULT]
prefs =
dom.reporting.enabled=true
dom.reporting.crash.enabled=true
dom.reporting.header.enabled=true
dom.reporting.testing.enabled=true
dom.reporting.delivering.timeout=1
dom.reporting.cleanup.timeout=1
privacy.userContext.enabled=1
support-files =
delivering.sjs
head.js
empty.html
[browser_cleanup.js]
[browser_content_crash.js]

Просмотреть файл

@ -2,42 +2,10 @@
const TEST_HOST = "example.org";
const TEST_DOMAIN = "https://" + TEST_HOST;
const TEST_PATH = "/browser/dom/reporting/tests/";
const TEST_TOP_PAGE = TEST_DOMAIN + TEST_PATH + "empty.html";
const TEST_SJS = TEST_DOMAIN + TEST_PATH + "delivering.sjs";
async function storeReportingHeader(browser, extraParams = "") {
await SpecialPowers.spawn(
browser,
[{ url: TEST_SJS, extraParams }],
async obj => {
await content
.fetch(
obj.url +
"?task=header" +
(obj.extraParams.length ? "&" + obj.extraParams : "")
)
.then(r => r.text())
.then(text => {
is(text, "OK", "Report-to header sent");
});
}
);
}
add_task(async function() {
await SpecialPowers.flushPrefEnv();
await SpecialPowers.pushPrefEnv({
set: [
["dom.reporting.enabled", true],
["dom.reporting.header.enabled", true],
["dom.reporting.testing.enabled", true],
["dom.reporting.delivering.timeout", 1],
["dom.reporting.cleanup.timeout", 1],
["privacy.userContext.enabled", true],
],
});
});
const TEST_PATH = "/dom/reporting/tests/";
const TEST_TOP_PAGE = TEST_DOMAIN + "/browser" + TEST_PATH + "empty.html";
const TEST_SJS = TEST_DOMAIN + "/tests" + TEST_PATH + "delivering.sjs";
add_task(async function() {
info("Testing a total cleanup");
@ -53,7 +21,7 @@ add_task(async function() {
"No data before the test"
);
await storeReportingHeader(browser);
await storeReportingHeader(browser, TEST_SJS);
ok(ChromeUtils.hasReportingHeaderForOrigin(TEST_DOMAIN), "We have data");
await new Promise(resolve => {
@ -85,7 +53,7 @@ add_task(async function() {
"No data before the test"
);
await storeReportingHeader(browser);
await storeReportingHeader(browser, TEST_SJS);
ok(ChromeUtils.hasReportingHeaderForOrigin(TEST_DOMAIN), "We have data");
await new Promise(resolve => {
@ -117,7 +85,7 @@ add_task(async function() {
"No data before the test"
);
await storeReportingHeader(browser);
await storeReportingHeader(browser, TEST_SJS);
ok(ChromeUtils.hasReportingHeaderForOrigin(TEST_DOMAIN), "We have data");
await new Promise(resolve => {
@ -152,7 +120,7 @@ add_task(async function() {
"No data before the test"
);
await storeReportingHeader(browser, "410=true");
await storeReportingHeader(browser, TEST_SJS, "410=true");
ok(ChromeUtils.hasReportingHeaderForOrigin(TEST_DOMAIN), "We have data");
await SpecialPowers.spawn(browser, [], async _ => {
@ -210,7 +178,7 @@ add_task(async function() {
"No data before the test"
);
await storeReportingHeader(browser);
await storeReportingHeader(browser, TEST_SJS);
ok(
!ChromeUtils.hasReportingHeaderForOrigin(TEST_DOMAIN),
"We don't have data for the origin"
@ -249,7 +217,7 @@ add_task(async function() {
"No data before the test"
);
await storeReportingHeader(browser);
await storeReportingHeader(browser, TEST_SJS);
ok(
ChromeUtils.hasReportingHeaderForOrigin(TEST_DOMAIN),
"We have data for the origin"
@ -273,4 +241,7 @@ add_task(async function() {
resolve()
);
});
// need to check the reports, to cleanup the server state.
await checkReport(TEST_SJS);
});

Просмотреть файл

@ -0,0 +1,54 @@
/* eslint-disable mozilla/no-arbitrary-setTimeout */
"use strict";
const TEST_HOST = "example.org";
const TEST_DOMAIN = "https://" + TEST_HOST;
const TEST_PATH = "/dom/reporting/tests/";
const TEST_TOP_PAGE = TEST_DOMAIN + "/browser" + TEST_PATH + "empty.html";
const TEST_SJS = TEST_DOMAIN + "/tests" + TEST_PATH + "delivering.sjs";
function crash_content(browser) {
browser.messageManager.loadFrameScript(
"data:,Components.classes['@mozilla.org/xpcom/debug;1'].getService(Components.interfaces.nsIDebug2).rustPanic('OH NO');",
false
);
}
add_task(async function() {
let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
gBrowser.selectedTab = tab;
let browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
ok(
!ChromeUtils.hasReportingHeaderForOrigin(TEST_DOMAIN),
"No data before the test"
);
await storeReportingHeader(browser, TEST_SJS);
ok(
ChromeUtils.hasReportingHeaderForOrigin(TEST_DOMAIN),
"We have data for the origin"
);
crash_content(browser);
const reports = await checkReport(TEST_SJS);
is(reports.length, 1, "We have 1 report");
const report = reports[0];
is(report.contentType, "application/reports+json", "Correct mime-type");
is(report.origin, "https://example.org", "Origin correctly set");
is(
report.url,
"https://example.org/tests/dom/reporting/tests/delivering.sjs",
"URL is correctly set"
);
ok(!!report.body, "We have a report.body");
ok(report.body.age > 0, "Age is correctly set");
is(report.body.url, TEST_TOP_PAGE, "URL is correctly set");
is(report.body.user_agent, navigator.userAgent, "User-agent matches");
is(report.body.type, "crash", "Type is fine.");
BrowserTestUtils.removeTab(tab);
});

Просмотреть файл

@ -0,0 +1,37 @@
"use strict";
/* exported storeReportingHeader */
async function storeReportingHeader(browser, reportingURL, extraParams = "") {
await SpecialPowers.spawn(
browser,
[{ url: reportingURL, extraParams }],
async obj => {
await content
.fetch(
obj.url +
"?task=header" +
(obj.extraParams.length ? "&" + obj.extraParams : "")
)
.then(r => r.text())
.then(text => {
is(text, "OK", "Report-to header sent");
});
}
);
}
/* exported checkReport */
function checkReport(reportingURL) {
return new Promise(resolve => {
let id = setInterval(_ => {
fetch(reportingURL + "?task=check")
.then(r => r.text())
.then(text => {
if (text) {
resolve(JSON.parse(text));
clearInterval(id);
}
});
}, 1000);
});
}

Просмотреть файл

@ -2143,6 +2143,11 @@
value: @IS_NIGHTLY_BUILD@
mirror: always
- name: dom.reporting.crash.enabled
type: RelaxedAtomicBool
value: false
mirror: always
- name: dom.reporting.header.enabled
type: RelaxedAtomicBool
value: false

Просмотреть файл

@ -3634,6 +3634,22 @@ bool SetRemoteExceptionHandler(const nsACString& crashPipe) {
}
#endif // XP_WIN
void GetAnnotation(uint32_t childPid, Annotation annotation,
nsACString& outStr) {
if (!GetEnabled()) {
return;
}
MutexAutoLock lock(*dumpMapLock);
ChildProcessData* pd = pidToMinidump->GetEntry(childPid);
if (!pd) {
return;
}
outStr = (*pd->annotations)[annotation];
}
bool TakeMinidumpForChild(uint32_t childPid, nsIFile** dump,
AnnotationTable& aAnnotations, uint32_t* aSequence) {
if (!GetEnabled()) return false;

Просмотреть файл

@ -117,6 +117,9 @@ nsresult UnregisterAppMemory(void* ptr);
// Include heap regions of the crash context.
void SetIncludeContextHeap(bool aValue);
void GetAnnotation(uint32_t childPid, Annotation annotation,
nsACString& outStr);
// Functions for working with minidumps and .extras
typedef mozilla::EnumeratedArray<Annotation, Annotation::Count, nsCString>
AnnotationTable;