Bug 765285 - Include last 3 days of crash IDs in about:support. r=Mossop,dolske

This commit is contained in:
Sebastian Hengst 2013-10-28 00:18:14 +01:00
parent e8e8b45530
commit 20f5e19299
9 changed files with 282 additions and 90 deletions

View File

@ -6,6 +6,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/Troubleshoot.jsm");
Components.utils.import("resource://gre/modules/PluralForm.jsm");
Components.utils.import("resource://gre/modules/ResetProfile.jsm");
window.addEventListener("load", function onload(event) {
@ -32,6 +33,73 @@ let snapshotFormatters = {
$("version-box").textContent = version;
},
#ifdef MOZ_CRASHREPORTER
crashes: function crashes(data) {
let strings = stringBundle();
let daysRange = Troubleshoot.kMaxCrashAge / (24 * 60 * 60 * 1000);
$("crashes-title").textContent =
PluralForm.get(daysRange, strings.GetStringFromName("crashesTitle"))
.replace("#1", daysRange);
let reportURL;
try {
reportURL = Services.prefs.getCharPref("breakpad.reportURL");
// Ignore any non http/https urls
if (!/^https?:/i.test(reportURL))
reportURL = null;
}
catch (e) { }
if (!reportURL) {
$("crashes-noConfig").style.display = "block";
$("crashes-noConfig").classList.remove("no-copy");
return;
}
else {
$("crashes-allReports").style.display = "block";
$("crashes-allReports").classList.remove("no-copy");
}
if (data.pending > 0) {
$("crashes-allReportsWithPending").textContent =
PluralForm.get(data.pending, strings.GetStringFromName("pendingReports"))
.replace("#1", data.pending);
}
let dateNow = new Date();
$.append($("crashes-tbody"), data.submitted.map(function (crash) {
let date = new Date(crash.date);
let timePassed = dateNow - date;
let formattedDate;
if (timePassed >= 24 * 60 * 60 * 1000)
{
let daysPassed = Math.round(timePassed / (24 * 60 * 60 * 1000));
let daysPassedString = strings.GetStringFromName("crashesTimeDays");
formattedDate = PluralForm.get(daysPassed, daysPassedString)
.replace("#1", daysPassed);
}
else if (timePassed >= 60 * 60 * 1000)
{
let hoursPassed = Math.round(timePassed / (60 * 60 * 1000));
let hoursPassedString = strings.GetStringFromName("crashesTimeHours");
formattedDate = PluralForm.get(hoursPassed, hoursPassedString)
.replace("#1", hoursPassed);
}
else
{
let minutesPassed = Math.max(Math.round(timePassed / (60 * 1000)), 1);
let minutesPassedString = strings.GetStringFromName("crashesTimeMinutes");
formattedDate = PluralForm.get(minutesPassed, minutesPassedString)
.replace("#1", minutesPassed);
}
return $.new("tr", [
$.new("td", [
$.new("a", crash.id, null, {href : reportURL + crash.id})
]),
$.new("td", formattedDate)
]);
}));
},
#endif
extensions: function extensions(data) {
$.append($("extensions-tbody"), data.map(function (extension) {
return $.new("tr", [
@ -212,10 +280,14 @@ let snapshotFormatters = {
let $ = document.getElementById.bind(document);
$.new = function $_new(tag, textContentOrChildren, className) {
$.new = function $_new(tag, textContentOrChildren, className, attributes) {
let elt = document.createElement(tag);
if (className)
elt.className = className;
if (attributes) {
for (let attrName in attributes)
elt.setAttribute(attrName, attributes[attrName]);
}
if (Array.isArray(textContentOrChildren))
this.append(elt, textContentOrChildren);
else

View File

@ -152,18 +152,6 @@
</td>
</tr>
#ifdef MOZ_CRASHREPORTER
<tr class="no-copy">
<th class="column">
&aboutSupport.appBasicsCrashIDs;
</th>
<td>
<a href="about:crashes">about:crashes</a>
</td>
</tr>
#endif
<tr class="no-copy">
<th class="column">
&aboutSupport.appBasicsMemoryUse;
@ -178,6 +166,33 @@
</table>
<!-- - - - - - - - - - - - - - - - - - - - - -->
#ifdef MOZ_CRASHREPORTER
<h2 class="major-section" id="crashes-title">
&aboutSupport.crashes.title;
</h2>
<table id="crashes-table">
<thead>
<tr>
<th>
&aboutSupport.crashes.id;
</th>
<th>
&aboutSupport.crashes.sendDate;
</th>
</tr>
</thead>
<tbody id="crashes-tbody">
</tbody>
</table>
<p id="crashes-allReports" class="no-copy" style="display: none">
<a href="about:crashes" id="crashes-allReportsWithPending" style="display: block">&aboutSupport.crashes.allReports;</a>
</p>
<p id="crashes-noConfig" class="no-copy" style="display: none">&aboutSupport.crashes.noConfig;</p>
#endif
<!-- - - - - - - - - - - - - - - - - - - - - -->
<h2 class="major-section">
&aboutSupport.extensionsTitle;

View File

@ -0,0 +1,91 @@
/* 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/. */
Components.utils.import("resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = [
"CrashReports"
];
this.CrashReports = {
pendingDir: null,
reportsDir: null,
submittedDir: null,
getReports: function CrashReports_getReports()
{
let reports = [];
try {
// Ignore any non http/https urls
if (!/^https?:/i.test(Services.prefs.getCharPref("breakpad.reportURL")))
return reports;
}
catch (e) { }
if (this.submittedDir.exists() && this.submittedDir.isDirectory()) {
let entries = this.submittedDir.directoryEntries;
while (entries.hasMoreElements()) {
let file = entries.getNext().QueryInterface(Components.interfaces.nsIFile);
let leaf = file.leafName;
if (leaf.startsWith("bp-") &&
leaf.endsWith(".txt")) {
let entry = {
id: leaf.slice(0, -4),
date: file.lastModifiedTime,
pending: false
};
reports.push(entry);
}
}
}
if (this.pendingDir.exists() && this.pendingDir.isDirectory()) {
let uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
let entries = this.pendingDir.directoryEntries;
while (entries.hasMoreElements()) {
let file = entries.getNext().QueryInterface(Components.interfaces.nsIFile);
let leaf = file.leafName;
let id = leaf.slice(0, -4);
if (leaf.endsWith(".dmp") && uuidRegex.test(id)) {
let entry = {
id: id,
date: file.lastModifiedTime,
pending: true
};
reports.push(entry);
}
}
}
// Sort reports descending by date
return reports.sort( (a, b) => b.date - a.date);
}
}
function CrashReports_pendingDir()
{
let pendingDir = Services.dirsvc.get("UAppData", Components.interfaces.nsIFile);
pendingDir.append("Crash Reports");
pendingDir.append("pending");
return pendingDir;
}
function CrashReports_reportsDir()
{
let reportsDir = Services.dirsvc.get("UAppData", Components.interfaces.nsIFile);
reportsDir.append("Crash Reports");
return reportsDir;
}
function CrashReports_submittedDir()
{
let submittedDir = Services.dirsvc.get("UAppData", Components.interfaces.nsIFile);
submittedDir.append("Crash Reports");
submittedDir.append("submitted");
return submittedDir;
}
this.CrashReports.pendingDir = CrashReports_pendingDir();
this.CrashReports.reportsDir = CrashReports_reportsDir();
this.CrashReports.submittedDir = CrashReports_submittedDir();

View File

@ -5,9 +5,9 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
var reportsDir, submittedDir, pendingDir;
var reportURL;
Components.utils.import("resource://gre/modules/CrashReports.jsm");
Components.utils.import("resource://gre/modules/CrashSubmit.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
@ -55,26 +55,6 @@ function submitPendingReport(event) {
return false;
}
function findInsertionPoint(reports, date) {
if (reports.length == 0)
return 0;
var min = 0;
var max = reports.length - 1;
while (min < max) {
var mid = parseInt((min + max) / 2);
if (reports[mid].date < date)
max = mid - 1;
else if (reports[mid].date > date)
min = mid + 1;
else
return mid;
}
if (reports[min].date <= date)
return min;
return min+1;
}
function populateReportList() {
var prefService = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
@ -92,57 +72,7 @@ function populateReportList() {
document.getElementById("noConfig").style.display = "block";
return;
}
var directoryService = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
reportsDir = directoryService.get("UAppData", Ci.nsIFile);
reportsDir.append("Crash Reports");
submittedDir = directoryService.get("UAppData", Ci.nsIFile);
submittedDir.append("Crash Reports");
submittedDir.append("submitted");
var reports = [];
if (submittedDir.exists() && submittedDir.isDirectory()) {
var entries = submittedDir.directoryEntries;
while (entries.hasMoreElements()) {
var file = entries.getNext().QueryInterface(Ci.nsIFile);
var leaf = file.leafName;
if (leaf.substr(0, 3) == "bp-" &&
leaf.substr(-4) == ".txt") {
var entry = {
id: leaf.slice(0, -4),
date: file.lastModifiedTime,
pending: false
};
var pos = findInsertionPoint(reports, entry.date);
reports.splice(pos, 0, entry);
}
}
}
pendingDir = directoryService.get("UAppData", Ci.nsIFile);
pendingDir.append("Crash Reports");
pendingDir.append("pending");
if (pendingDir.exists() && pendingDir.isDirectory()) {
var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
var entries = pendingDir.directoryEntries;
while (entries.hasMoreElements()) {
var file = entries.getNext().QueryInterface(Ci.nsIFile);
var leaf = file.leafName;
var id = leaf.slice(0, -4);
if (leaf.substr(-4) == ".dmp" && uuidRegex.test(id)) {
var entry = {
id: id,
date: file.lastModifiedTime,
pending: true
};
var pos = findInsertionPoint(reports, entry.date);
reports.splice(pos, 0, entry);
}
}
}
let reports = CrashReports.getReports();
if (reports.length == 0) {
document.getElementById("clear-reports").style.display = "none";
@ -208,7 +138,7 @@ function clearReports() {
bundle.GetStringFromName("deleteconfirm.description")))
return;
var entries = submittedDir.directoryEntries;
var entries = CrashReports.submittedDir.directoryEntries;
while (entries.hasMoreElements()) {
var file = entries.getNext().QueryInterface(Ci.nsIFile);
var leaf = file.leafName;
@ -217,7 +147,7 @@ function clearReports() {
file.remove(false);
}
}
entries = reportsDir.directoryEntries;
entries = CrashReports.reportsDir.directoryEntries;
var oneYearAgo = Date.now() - 31586000000;
while (entries.hasMoreElements()) {
var file = entries.getNext().QueryInterface(Ci.nsIFile);
@ -228,7 +158,7 @@ function clearReports() {
file.remove(false);
}
}
entries = pendingDir.directoryEntries;
entries = CrashReports.pendingDir.directoryEntries;
while (entries.hasMoreElements()) {
entries.getNext().QueryInterface(Ci.nsIFile).remove(false);
}

View File

@ -73,6 +73,7 @@ if CONFIG['OS_ARCH'] == 'Darwin':
]
EXTRA_JS_MODULES += [
'CrashReports.jsm',
'CrashSubmit.jsm',
'KeyValueParser.jsm',
]

View File

@ -11,6 +11,16 @@
about &brandShortName;, check out our <a id='supportLink'>support website</a>.
">
<!ENTITY aboutSupport.crashes.title "Crash Reports">
<!-- LOCALIZATION NOTE (aboutSupport.crashes.id):
This is likely the same like id.heading in crashes.dtd. -->
<!ENTITY aboutSupport.crashes.id "Report ID">
<!-- LOCALIZATION NOTE (aboutSupport.crashes.id):
This is likely the same like date.heading in crashes.dtd. -->
<!ENTITY aboutSupport.crashes.sendDate "Submitted">
<!ENTITY aboutSupport.crashes.allReports "All Crash Reports">
<!ENTITY aboutSupport.crashes.noConfig "This application has not been configured to display crash reports.">
<!ENTITY aboutSupport.extensionsTitle "Extensions">
<!ENTITY aboutSupport.extensionName "Name">
<!ENTITY aboutSupport.extensionEnabled "Enabled">
@ -35,7 +45,6 @@ Windows/Mac use the term "Folder" instead of "Directory" -->
<!ENTITY aboutSupport.appBasicsEnabledPlugins "Enabled Plugins">
<!ENTITY aboutSupport.appBasicsBuildConfig "Build Configuration">
<!ENTITY aboutSupport.appBasicsUserAgent "User Agent">
<!ENTITY aboutSupport.appBasicsCrashIDs "Crash Reports">
<!ENTITY aboutSupport.appBasicsMemoryUse "Memory Use">
<!ENTITY aboutSupport.showDir.label "Open Directory">

View File

@ -2,6 +2,31 @@
# 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/.
# LOCALIZATION NOTE (downloadsTitleFiles): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of days relevant with relevant crash reports
crashesTitle=Crash Reports for the Last #1 Day;Crash Reports for the Last #1 Days
# LOCALIZATION NOTE (crashesTimeMinutes): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of minutes (between 1 and 59) which have passed since the crash
crashesTimeMinutes=#1 minute ago;#1 minutes ago
# LOCALIZATION NOTE (crashesTimeHours): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of hours (between 1 and 23) which have passed since the crash
crashesTimeHours=#1 hour ago;#1 hours ago
# LOCALIZATION NOTE (crashesTimeDays): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of days (1 or more) which have passed since the crash
crashesTimeDays=#1 day ago;#1 days ago
# LOCALIZATION NOTE (downloadsTitleFiles): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of pending crash reports
pendingReports=All Crash Reports (including #1 pending crash in the given time range);All Crash Reports (including #1 pending crashes in the given time range)
# LOCALIZATION NOTE In the following strings, "Direct2D", "DirectWrite" and "ClearType"
# are proper nouns and should not be translated. Feel free to leave english strings if
# there are no good translations, these are only used in about:support

View File

@ -8,8 +8,12 @@ this.EXPORTED_SYMBOLS = [
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
#ifdef MOZ_CRASHREPORTER
Cu.import("resource://gre/modules/CrashReports.jsm");
#endif
// We use a preferences whitelist to make sure we only show preferences that
// are useful for support and won't compromise the user's privacy. Note that
@ -104,6 +108,8 @@ this.Troubleshoot = {
}
}
},
kMaxCrashAge: 3 * 24 * 60 * 60 * 1000, // 3 days
};
// Each data provider is a name => function mapping. When a snapshot is
@ -134,6 +140,18 @@ let dataProviders = {
done(data);
},
#ifdef MOZ_CRASHREPORTER
crashes: function crashes(done) {
let reports = CrashReports.getReports();
let now = new Date();
let reportsNew = reports.filter(report => (now - report.date < Troubleshoot.kMaxCrashAge));
let reportsSubmitted = reportsNew.filter(report => (!report.pending));
let reportsPendingCount = reportsNew.length - reportsSubmitted.length;
let data = {submitted : reportsSubmitted, pending : reportsPendingCount};
done(data);
},
#endif
extensions: function extensions(done) {
AddonManager.getAddonsByTypes(["extension"], function (extensions) {
extensions.sort(function (a, b) {

View File

@ -100,6 +100,37 @@ const SNAPSHOT_SCHEMA = {
},
},
},
crashes: {
required: false,
type: "object",
properties: {
pending: {
required: true,
type: "number",
},
submitted: {
required: true,
type: "array",
items: {
type: "object",
properties: {
id: {
required: true,
type: "string",
},
date: {
required: true,
type: "number",
},
pending: {
required: true,
type: "boolean",
},
},
},
},
},
},
extensions: {
required: true,
type: "array",