commit a27819dbf46ad5b682c767aaeeed7a3c5d6e5ffe
parent 8835f200186d7a983e8c4a594f06f4106458ffdb
Author: Daisuke Akatsuka <daisuke@birchill.co.jp>
Date: Thu, 11 Dec 2025 02:24:16 +0000
Bug 1797758: Add additional places database info in about:support r=mak,fluent-reviewers,bolsson
Differential Revision: https://phabricator.services.mozilla.com/D273174
Diffstat:
6 files changed, 276 insertions(+), 68 deletions(-)
diff --git a/browser/base/content/test/about/browser_aboutSupport_places.js b/browser/base/content/test/about/browser_aboutSupport_places.js
@@ -3,43 +3,154 @@
"use strict";
+const CORRUPT_FILE_PATH = PathUtils.join(
+ PathUtils.profileDir,
+ "places.sqlite.corrupt"
+);
+
+add_setup(async function setup() {
+ registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref("places.database.lastMaintenance");
+ Services.prefs.clearUserPref("storage.vacuum.last.places.sqlite");
+ await IOUtils.remove(CORRUPT_FILE_PATH);
+ });
+});
+
add_task(async function test_places_db_stats_table() {
- await BrowserTestUtils.withNewTab(
- { gBrowser, url: "about:support" },
- async function (browser) {
- const [initialToggleText, toggleTextAfterShow, toggleTextAfterHide] =
- await SpecialPowers.spawn(browser, [], async function () {
- const toggleButton = content.document.getElementById(
- "place-database-stats-toggle"
- );
- const getToggleText = () =>
- content.document.l10n.getAttributes(toggleButton).id;
- const toggleTexts = [];
- const table = content.document.getElementById(
- "place-database-stats-tbody"
- );
- await ContentTaskUtils.waitForCondition(
- () => ContentTaskUtils.isHidden(table),
- "Stats table is hidden initially"
- );
- toggleTexts.push(getToggleText());
- toggleButton.click();
- await ContentTaskUtils.waitForCondition(
- () => ContentTaskUtils.isVisible(table),
- "Stats table is shown after first toggle"
- );
- toggleTexts.push(getToggleText());
- toggleButton.click();
- await ContentTaskUtils.waitForCondition(
- () => ContentTaskUtils.isHidden(table),
- "Stats table is hidden after second toggle"
- );
- toggleTexts.push(getToggleText());
- return toggleTexts;
- });
- Assert.equal(initialToggleText, "place-database-stats-show");
- Assert.equal(toggleTextAfterShow, "place-database-stats-hide");
- Assert.equal(toggleTextAfterHide, "place-database-stats-show");
+ await doTestOnAboutSupportPage(async function (browser) {
+ const [initialToggleText, toggleTextAfterShow, toggleTextAfterHide] =
+ await SpecialPowers.spawn(browser, [], async function () {
+ const toggleButton = content.document.getElementById(
+ "place-database-stats-toggle"
+ );
+ const getToggleText = () =>
+ content.document.l10n.getAttributes(toggleButton).id;
+ const toggleTexts = [];
+ const table = content.document.getElementById(
+ "place-database-stats-tbody"
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isHidden(table),
+ "Stats table is hidden initially"
+ );
+ toggleTexts.push(getToggleText());
+ toggleButton.click();
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(table),
+ "Stats table is shown after first toggle"
+ );
+ toggleTexts.push(getToggleText());
+ toggleButton.click();
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isHidden(table),
+ "Stats table is hidden after second toggle"
+ );
+ toggleTexts.push(getToggleText());
+ return toggleTexts;
+ });
+ Assert.equal(initialToggleText, "place-database-stats-show");
+ Assert.equal(toggleTextAfterShow, "place-database-stats-hide");
+ Assert.equal(toggleTextAfterHide, "place-database-stats-show");
+ });
+});
+
+add_task(async function test_lastMaintenanceDate() {
+ const TEST_DATA = [
+ {
+ date: 0,
+ expected: "Missing",
+ },
+ {
+ date: Math.floor(new Date("2025-11-21T12:16:30").getTime() / 1000),
+ expected: "11/21/2025, 12:16 PM",
+ },
+ ];
+
+ for (const { date, expected } of TEST_DATA) {
+ Services.prefs.setIntPref("places.database.lastMaintenance", date);
+
+ await doTestOnAboutSupportPage(async function (browser) {
+ await waitUntilContentLabelUpdated({
+ browser,
+ id: "place-database-last-idle-maintenance-data",
+ expected,
+ });
+ Assert.ok(true, "The lastMaintenance is displayed correctly");
+ });
+ }
+});
+
+add_task(async function test_lastVacuumDate() {
+ const TEST_DATA = [
+ {
+ date: 0,
+ expected: "Missing",
+ },
+ {
+ date: Math.floor(new Date("2025-11-21T12:16:30").getTime() / 1000),
+ expected: "11/21/2025, 12:16 PM",
+ },
+ ];
+
+ for (const { date, expected } of TEST_DATA) {
+ Services.prefs.setIntPref("storage.vacuum.last.places.sqlite", date);
+
+ await doTestOnAboutSupportPage(async function (browser) {
+ await waitUntilContentLabelUpdated({
+ browser,
+ id: "place-database-last-vacuum-date",
+ expected,
+ });
+ Assert.ok(true, "The lastVacuumDate is displayed correctly");
+ });
+ }
+});
+
+add_task(async function test_lastIntegrityCorruptionDate() {
+ const TEST_DATA = [
+ {
+ // No corruption file.
+ date: 0,
+ expected: "Missing",
+ },
+ {
+ date: new Date("2025-11-21T12:16:30").getTime(),
+ expected: "11/21/2025, 12:16 PM",
+ },
+ ];
+
+ for (const { date, expected } of TEST_DATA) {
+ if (date) {
+ await IOUtils.writeUTF8(CORRUPT_FILE_PATH, "test");
+ await IOUtils.setModificationTime(CORRUPT_FILE_PATH, date);
}
- );
+
+ await doTestOnAboutSupportPage(async function (browser) {
+ await waitUntilContentLabelUpdated({
+ browser,
+ id: "place-database-last-integrity-corruption-date",
+ expected,
+ });
+ Assert.ok(true, "The lastIntegrityCorruptionDate is displayed correctly");
+ });
+ }
});
+
+async function doTestOnAboutSupportPage(func) {
+ await BrowserTestUtils.withNewTab({ gBrowser, url: "about:support" }, func);
+}
+
+async function waitUntilContentLabelUpdated({ browser, id, expected }) {
+ await SpecialPowers.spawn(browser, [id, expected], async (i, e) => {
+ const target = content.document.getElementById(i);
+ await ContentTaskUtils.waitForMutationCondition(
+ target,
+ {
+ characterData: true,
+ childList: true,
+ subtree: true,
+ },
+ () => target.textContent == e
+ );
+ });
+}
diff --git a/toolkit/content/aboutSupport.js b/toolkit/content/aboutSupport.js
@@ -410,10 +410,11 @@ var snapshotFormatters = {
if (!AppConstants.MOZ_PLACES) {
return;
}
+ const { prefs } = data;
const statsBody = $("place-database-stats-tbody");
$.append(
statsBody,
- data.map(function (entry) {
+ prefs.map(function (entry) {
return $.new("tr", [
$.new("td", entry.entity),
$.new("td", entry.count),
@@ -443,6 +444,45 @@ var snapshotFormatters = {
}
}
);
+
+ const formatter = new Intl.DateTimeFormat(undefined, {
+ year: "numeric",
+ month: "numeric",
+ day: "numeric",
+ hour: "numeric",
+ minute: "numeric",
+ });
+
+ const maintenanceDateElement = $(
+ "place-database-last-idle-maintenance-data"
+ );
+ if (data.lastMaintenanceDate) {
+ maintenanceDateElement.textContent = formatter.format(
+ new Date(data.lastMaintenanceDate)
+ );
+ } else {
+ document.l10n.setAttributes(maintenanceDateElement, "missing");
+ }
+
+ const vacuumDateElement = $("place-database-last-vacuum-date");
+ if (data.lastVacuumDate) {
+ vacuumDateElement.textContent = formatter.format(
+ new Date(data.lastVacuumDate)
+ );
+ } else {
+ document.l10n.setAttributes(vacuumDateElement, "missing");
+ }
+
+ const integrityCorruptionDateElement = $(
+ "place-database-last-integrity-corruption-date"
+ );
+ if (data.lastIntegrityCorruptionDate) {
+ integrityCorruptionDateElement.textContent = formatter.format(
+ new Date(data.lastIntegrityCorruptionDate)
+ );
+ } else {
+ document.l10n.setAttributes(integrityCorruptionDateElement, "missing");
+ }
},
printingPreferences(data) {
diff --git a/toolkit/content/aboutSupport.xhtml b/toolkit/content/aboutSupport.xhtml
@@ -721,6 +721,20 @@
<th data-l10n-id="place-database-stats-sequentiality-perc"/>
</tr>
</tbody>
+ <tbody>
+ <tr>
+ <th class="column" data-l10n-id="place-database-last-idle-maintenance-data"/>
+ <td colspan="5" id="place-database-last-idle-maintenance-data"></td>
+ </tr>
+ <tr>
+ <th class="column" data-l10n-id="place-database-last-vacuum-date"/>
+ <td colspan="5" id="place-database-last-vacuum-date"></td>
+ </tr>
+ <tr>
+ <th class="column" data-l10n-id="place-database-last-integrity-corruption-date"/>
+ <td colspan="5" id="place-database-last-integrity-corruption-date"></td>
+ </tr>
+ </tbody>
</table>
#endif
diff --git a/toolkit/locales/en-US/toolkit/about/aboutSupport.ftl b/toolkit/locales/en-US/toolkit/about/aboutSupport.ftl
@@ -131,6 +131,10 @@ place-database-stats-efficiency-perc = Efficiency (%)
place-database-stats-sequentiality-perc = Sequentiality (%)
place-database-integrity = Integrity
place-database-verify-integrity = Verify Integrity
+place-database-last-idle-maintenance-data = Last Idle Maintenance Date
+# Vacuum refers to a type of database maintenance process
+place-database-last-vacuum-date = Last Vacuum Date
+place-database-last-integrity-corruption-date = Last Integrity Corruption Date
a11y-title = Accessibility
a11y-activated = Activated
a11y-force-disabled = Prevent Accessibility
diff --git a/toolkit/modules/Troubleshoot.sys.mjs b/toolkit/modules/Troubleshoot.sys.mjs
@@ -513,9 +513,30 @@ var dataProviders = {
},
places: async function places(done) {
- const data = AppConstants.MOZ_PLACES
- ? await lazy.PlacesDBUtils.getEntitiesStatsAndCounts()
- : [];
+ const data = {};
+
+ if (AppConstants.MOZ_PLACES) {
+ data.prefs = await lazy.PlacesDBUtils.getEntitiesStatsAndCounts();
+
+ data.lastMaintenanceDate =
+ Services.prefs.getIntPref("places.database.lastMaintenance", 0) * 1000;
+ data.lastVacuumDate =
+ Services.prefs.getIntPref("storage.vacuum.last.places.sqlite", 0) *
+ 1000;
+
+ try {
+ const corruptFilePath = PathUtils.join(
+ PathUtils.profileDir,
+ "places.sqlite.corrupt"
+ );
+ const fileInfo = await IOUtils.stat(corruptFilePath);
+ data.lastIntegrityCorruptionDate = fileInfo.lastModified;
+ } catch (e) {
+ // Set 0 if failed such the file not found error.
+ data.lastIntegrityCorruptionDate = 0;
+ }
+ }
+
done(data);
},
diff --git a/toolkit/modules/tests/browser/browser_Troubleshoot.js b/toolkit/modules/tests/browser/browser_Troubleshoot.js
@@ -493,35 +493,53 @@ const SNAPSHOT_SCHEMA = {
},
places: {
required: true,
- type: "array",
- items: {
- type: "object",
- items: {
- entity: {
- required: true,
- type: "string",
- },
- count: {
- required: true,
- type: "number",
- },
- sizeBytes: {
- required: true,
- type: "number",
- },
- sizePerc: {
- required: true,
- type: "number",
- },
- efficiencyPerc: {
- required: true,
- type: "number",
- },
- sequentialityPerc: {
- required: true,
- type: "number",
+ type: "object",
+ properties: {
+ prefs: {
+ required: true,
+ type: "array",
+ items: {
+ type: "object",
+ items: {
+ entity: {
+ required: true,
+ type: "string",
+ },
+ count: {
+ required: true,
+ type: "number",
+ },
+ sizeBytes: {
+ required: true,
+ type: "number",
+ },
+ sizePerc: {
+ required: true,
+ type: "number",
+ },
+ efficiencyPerc: {
+ required: true,
+ type: "number",
+ },
+ sequentialityPerc: {
+ required: true,
+ type: "number",
+ },
+ },
},
},
+ lastMaintenanceDate: {
+ required: true,
+ type: "number",
+ },
+ lastVacuumDate: {
+ required: true,
+ type: "number",
+ },
+ lastIntegrityCorruptionDate: {
+ required: true,
+ type: "number",
+ },
},
},
graphics: {