tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mbrowser/base/content/test/about/browser_aboutSupport_places.js | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mtoolkit/content/aboutSupport.js | 42+++++++++++++++++++++++++++++++++++++++++-
Mtoolkit/content/aboutSupport.xhtml | 14++++++++++++++
Mtoolkit/locales/en-US/toolkit/about/aboutSupport.ftl | 4++++
Mtoolkit/modules/Troubleshoot.sys.mjs | 27++++++++++++++++++++++++---
Mtoolkit/modules/tests/browser/browser_Troubleshoot.js | 72+++++++++++++++++++++++++++++++++++++++++++++---------------------------
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: {