Bug 1964214 - Fix TypeScript and ESLint issues raised in PlacesDBUtils.sys.mjs and PlacesExpiration.sys.mjs - r=Standard8

Differential Revision: https://phabricator.services.mozilla.com/D247691
This commit is contained in:
James Teow
2025-05-12 23:41:26 +00:00
committed by jteow@mozilla.com
parent 77343be3df
commit 654aca0932
2 changed files with 177 additions and 84 deletions

View File

@@ -50,7 +50,7 @@ export var PlacesDBUtils = {
Services.prefs.setIntPref( Services.prefs.setIntPref(
"places.database.lastMaintenance", "places.database.lastMaintenance",
parseInt(Date.now() / 1000) Math.floor(Date.now() / 1000)
); );
Glean.places.idleMaintenanceTime.stopAndAccumulate(timerId); Glean.places.idleMaintenanceTime.stopAndAccumulate(timerId);
return taskStatusMap; return taskStatusMap;
@@ -87,7 +87,7 @@ export var PlacesDBUtils = {
* Note: although this function isn't actually async, we keep it async to * Note: although this function isn't actually async, we keep it async to
* allow us to maintain a simple, consistent API for the tasks within this object. * allow us to maintain a simple, consistent API for the tasks within this object.
* *
* @returns {Array} An empty array. * @returns {Promise<void[]>} An empty array.
*/ */
async _refreshUI() { async _refreshUI() {
PlacesObservers.notifyListeners([new PlacesPurgeCaches()]); PlacesObservers.notifyListeners([new PlacesPurgeCaches()]);
@@ -831,22 +831,22 @@ export var PlacesDBUtils = {
* Note: although this function isn't actually async, we keep it async to * Note: although this function isn't actually async, we keep it async to
* allow us to maintain a simple, consistent API for the tasks within this object. * allow us to maintain a simple, consistent API for the tasks within this object.
* *
* @returns {Promise} resolves when database is vacuumed. * @returns {Promise<Array<string>>}
* @resolves to an array of logs for this task. * Resolves when database is vacuumed to an array of logs for this task.
* @rejects if we are unable to vacuum database. * @rejects if we are unable to vacuum database.
*/ */
async vacuum() { async vacuum() {
let logs = []; let logs = [];
let placesDbPath = PathUtils.join(PathUtils.profileDir, "places.sqlite"); let placesDbPath = PathUtils.join(PathUtils.profileDir, "places.sqlite");
let info = await IOUtils.stat(placesDbPath); let info = await IOUtils.stat(placesDbPath);
logs.push(`Initial database size is ${parseInt(info.size / 1024)}KiB`); logs.push(`Initial database size is ${Math.floor(info.size / 1024)}KiB`);
return lazy.PlacesUtils.withConnectionWrapper( return lazy.PlacesUtils.withConnectionWrapper(
"PlacesDBUtils: vacuum", "PlacesDBUtils: vacuum",
async db => { async db => {
await db.execute("VACUUM"); await db.execute("VACUUM");
logs.push("The database has been vacuumed"); logs.push("The database has been vacuumed");
info = await IOUtils.stat(placesDbPath); info = await IOUtils.stat(placesDbPath);
logs.push(`Final database size is ${parseInt(info.size / 1024)}KiB`); logs.push(`Final database size is ${Math.floor(info.size / 1024)}KiB`);
return logs; return logs;
} }
).catch(() => { ).catch(() => {
@@ -883,8 +883,11 @@ export var PlacesDBUtils = {
); );
}); });
// Force an orphans expiration step. // Typescript sees that expiration can either be an object with the observe
expiration.observe(null, "places-debug-start-expiration", 0); // method or a function with the signature of observe.
if (typeof expiration !== "function") {
expiration.observe(null, "places-debug-start-expiration", "0");
}
return returnPromise; return returnPromise;
}, },
@@ -899,13 +902,13 @@ export var PlacesDBUtils = {
let logs = []; let logs = [];
let placesDbPath = PathUtils.join(PathUtils.profileDir, "places.sqlite"); let placesDbPath = PathUtils.join(PathUtils.profileDir, "places.sqlite");
let info = await IOUtils.stat(placesDbPath); let info = await IOUtils.stat(placesDbPath);
logs.push(`Places.sqlite size is ${parseInt(info.size / 1024)}KiB`); logs.push(`Places.sqlite size is ${Math.floor(info.size / 1024)}KiB`);
let faviconsDbPath = PathUtils.join( let faviconsDbPath = PathUtils.join(
PathUtils.profileDir, PathUtils.profileDir,
"favicons.sqlite" "favicons.sqlite"
); );
info = await IOUtils.stat(faviconsDbPath); info = await IOUtils.stat(faviconsDbPath);
logs.push(`Favicons.sqlite size is ${parseInt(info.size / 1024)}KiB`); logs.push(`Favicons.sqlite size is ${Math.floor(info.size / 1024)}KiB`);
// Execute each step async. // Execute each step async.
let pragmas = [ let pragmas = [
@@ -930,9 +933,19 @@ export var PlacesDBUtils = {
// Get maximum number of unique URIs. // Get maximum number of unique URIs.
try { try {
let limitURIs = await Cc["@mozilla.org/places/expiration;1"] /**
.getService(Ci.nsISupports) * A partial shape of nsPlacesExpiration, just for what we use in stats.
.wrappedJSObject.getPagesLimit(); *
* @typedef {object} ExpirationWrappedJSObject
* @property {function(): Promise<number>} getPagesLimit
*/
// This has to be type cast because wrappedJSObject is an object.
let expiration = /** @type {ExpirationWrappedJSObject} */ (
Cc["@mozilla.org/places/expiration;1"].getService(Ci.nsISupports)
.wrappedJSObject
);
let limitURIs = await expiration.getPagesLimit();
logs.push( logs.push(
"History can store a maximum of " + limitURIs + " unique pages" "History can store a maximum of " + limitURIs + " unique pages"
); );
@@ -966,10 +979,10 @@ export var PlacesDBUtils = {
return details.get(a).sizePerc - details.get(b).sizePerc; return details.get(a).sizePerc - details.get(b).sizePerc;
}); });
for (let key of entities) { for (let key of entities) {
let info = details.get(key); let value = details.get(key);
logs.push( logs.push(
`${key}: ${info.sizeBytes / 1024}KiB (${info.sizePerc}%), ${ `${key}: ${value.sizeBytes / 1024}KiB (${value.sizePerc}%), ${
info.efficiencyPerc value.efficiencyPerc
}% eff.` }% eff.`
); );
} }
@@ -1079,7 +1092,7 @@ export var PlacesDBUtils = {
"places.sqlite" "places.sqlite"
); );
let info = await IOUtils.stat(placesDbPath); let info = await IOUtils.stat(placesDbPath);
return parseInt(info.size / BYTES_PER_MEBIBYTE); return Math.floor(info.size / BYTES_PER_MEBIBYTE);
}, },
}, },
@@ -1091,7 +1104,7 @@ export var PlacesDBUtils = {
"favicons.sqlite" "favicons.sqlite"
); );
let info = await IOUtils.stat(faviconsDbPath); let info = await IOUtils.stat(faviconsDbPath);
return parseInt(info.size / BYTES_PER_MEBIBYTE); return Math.floor(info.size / BYTES_PER_MEBIBYTE);
}, },
}, },
@@ -1107,8 +1120,8 @@ export var PlacesDBUtils = {
let lastMaintenance = Services.prefs.getIntPref( let lastMaintenance = Services.prefs.getIntPref(
"places.database.lastMaintenance" "places.database.lastMaintenance"
); );
let nowSeconds = parseInt(Date.now() / 1000); let nowSeconds = Math.floor(Date.now() / 1000);
return parseInt((nowSeconds - lastMaintenance) / 86400); return Math.floor((nowSeconds - lastMaintenance) / 86400);
} catch (ex) { } catch (ex) {
return 60; return 60;
} }
@@ -1138,15 +1151,16 @@ export var PlacesDBUtils = {
// Report the result of the probe through Telemetry. // Report the result of the probe through Telemetry.
// The resulting promise cannot reject. // The resulting promise cannot reject.
if ("callback" in probe) { if ("callback" in probe) {
val = await probe.callback(val); val = await probe.callback();
} }
if (probe.distribution) { if (probe.distribution) {
// Memory distributions have the method named 'accumulate' // Memory distributions have the method named 'accumulate'
// instead of 'accumulateSingleSample'. // instead of 'accumulateSingleSample'.
( if ("accumulateSingleSample" in probe.distribution) {
probe.distribution.accumulateSingleSample || probe.distribution.accumulateSingleSample(val);
probe.distribution.accumulate } else if ("accumulate" in probe.distribution) {
).call(probe.distribution, val); probe.distribution.accumulate(val);
}
} else if (probe.quantity) { } else if (probe.quantity) {
probe.quantity.set(val); probe.quantity.set(val);
} else { } else {
@@ -1158,7 +1172,8 @@ export var PlacesDBUtils = {
/** /**
* Remove old and useless places.sqlite.corrupt files. * Remove old and useless places.sqlite.corrupt files.
* *
* @resolves to an array of logs for this task. * @returns {Promise<Array<string>>}
* Resolves to an array of logs for this task.
*/ */
async removeOldCorruptDBs() { async removeOldCorruptDBs() {
let logs = []; let logs = [];
@@ -1198,19 +1213,17 @@ export var PlacesDBUtils = {
/** /**
* Gets detailed statistics about database entities like tables and indices. * Gets detailed statistics about database entities like tables and indices.
* *
* @returns {Map} a Map by table name, containing an object with the following * @returns {Promise<Map<string, object>>}
* properties: * A Map by table name, containing an object with the following properties:
* - efficiencyPerc: percentage filling of pages, an high * - efficiencyPerc: percentage filling of pages, an high efficiency means
* efficiency means most pages are filled up almost completely. * most pages are filled up almost completely. This value is not
* This value is not particularly useful with a low number of * particularly useful with a low number of pages.
* pages. * - sizeBytes: size of the entity in bytes
* - sizeBytes: size of the entity in bytes * - pages: number of pages of the entity
* - pages: number of pages of the entity * - sizePerc: percentage of the total database size
* - sizePerc: percentage of the total database size * - sequentialityPerc: percentage of sequential pages, this is a global
* - sequentialityPerc: percentage of sequential pages, this is * value of the database, thus it's the same for every entity, and it can
* a global value of the database, thus it's the same for every * be used to evaluate fragmentation and the need for vacuum.
* entity, and it can be used to evaluate fragmentation and the
* need for vacuum.
*/ */
async getEntitiesStats() { async getEntitiesStats() {
let db = await lazy.PlacesUtils.promiseDBConnection(); let db = await lazy.PlacesUtils.promiseDBConnection();
@@ -1249,10 +1262,11 @@ export var PlacesDBUtils = {
* Gets detailed statistics about database entities and their respective row * Gets detailed statistics about database entities and their respective row
* counts. * counts.
* *
* @returns {Array} An array that augments each object returned by * @returns {Promise<Array<{entity: string, count: number}>>}
* {@link getEntitiesStats} with the following extra properties: * An array that augments each object returned by {@link getEntitiesStats}
* - entity: name of the entity * with the following extra properties:
* - count: row count of the entity * - entity: name of the entity
* - count: row count of the entity
*/ */
async getEntitiesStatsAndCounts() { async getEntitiesStatsAndCounts() {
let stats = await PlacesDBUtils.getEntitiesStats(); let stats = await PlacesDBUtils.getEntitiesStats();
@@ -1281,14 +1295,15 @@ export var PlacesDBUtils = {
/** /**
* Runs a list of tasks, returning a Map when done. * Runs a list of tasks, returning a Map when done.
* *
* @param tasks * @param {Array<Function>} tasks
* Array of tasks to be executed, in form of pointers to methods in * An array of tasks to be executed, in the form of pointers to methods in
* this module. * this module.
* @returns {Promise} *
* A promise that resolves with a Map[taskName(String) -> Object]. * @returns {Promise<Map<string, {succeeded: boolean, logs: Array<string>}>>}
* The Object has the following properties: * A promise that resolves with a Map. The key is the taskname (a string)
* - succeeded: boolean * and the value is an object with the following properties:
* - logs: an array of strings containing the messages logged by the task * - succeeded: Whether the task succeeded.
* - logs: An array of strings containing the messages logged by the task.
*/ */
async runTasks(tasks) { async runTasks(tasks) {
if (!this._registeredShutdownObserver) { if (!this._registeredShutdownObserver) {
@@ -1327,12 +1342,14 @@ export var PlacesDBUtils = {
async function integrity(dbName) { async function integrity(dbName) {
async function check(db) { async function check(db) {
/** @type {mozIStorageRow?} */
let row; let row;
await db.execute("PRAGMA integrity_check", null, (r, cancel) => { await db.execute("PRAGMA integrity_check", null, (r, cancel) => {
row = r; row = r;
cancel(); cancel();
}); });
return row.getResultByIndex(0) === "ok"; // @ts-ignore - nsIVariant has no overlap with other Javascript types
return row?.getResultByIndex(0) === "ok";
} }
// Create a new connection for this check, so we can operate independently // Create a new connection for this check, so we can operate independently
@@ -1352,7 +1369,7 @@ async function integrity(dbName) {
try { try {
await db.execute("REINDEX"); await db.execute("REINDEX");
} catch (ex) { } catch (ex) {
throw new Components.Exception( throw Components.Exception(
"Impossible to reindex database", "Impossible to reindex database",
Cr.NS_ERROR_FILE_CORRUPTED Cr.NS_ERROR_FILE_CORRUPTED
); );
@@ -1360,7 +1377,7 @@ async function integrity(dbName) {
// Check again. // Check again.
if (!(await check(db))) { if (!(await check(db))) {
throw new Components.Exception( throw Components.Exception(
"The database is still corrupt", "The database is still corrupt",
Cr.NS_ERROR_FILE_CORRUPTED Cr.NS_ERROR_FILE_CORRUPTED
); );
@@ -1381,7 +1398,7 @@ PlacesDBUtilsIdleMaintenance.prototype = {
"places.database.lastMaintenance", "places.database.lastMaintenance",
0 0
); );
let nowSeconds = parseInt(Date.now() / 1000); let nowSeconds = Math.floor(Date.now() / 1000);
if (lastMaintenance < nowSeconds - MAINTENANCE_INTERVAL_SECONDS) { if (lastMaintenance < nowSeconds - MAINTENANCE_INTERVAL_SECONDS) {
PlacesDBUtils.maintenanceOnIdle(); PlacesDBUtils.maintenanceOnIdle();
} }

View File

@@ -87,33 +87,64 @@ const OVERLIMIT_PAGES_THRESHOLD = 1000;
// Milliseconds in a day. // Milliseconds in a day.
const MSECS_PER_DAY = 86400000; const MSECS_PER_DAY = 86400000;
// When we expire we can use these limits: /**
// - SMALL for usual partial expirations, will expire a small chunk. * Represents the expiration limits.
// - LARGE for idle or shutdown expirations, will expire a large chunk. *
// - UNLIMITED will expire all the orphans. * @readonly
// - DEBUG will use a known limit, passed along with the debug notification. * @enum {number}
*/
const LIMIT = { const LIMIT = {
/** SMALL for usual partial expirations, will expire a small chunk. */
SMALL: 0, SMALL: 0,
/** LARGE for idle or shutdown expirations, will expire a large chunk. */
LARGE: 1, LARGE: 1,
/** UNLIMITED will expire all the orphans. */
UNLIMITED: 2, UNLIMITED: 2,
/** DEBUG will use a known limit, passed along with the debug notification. */
DEBUG: 3, DEBUG: 3,
}; };
// Represents the status of history database. /**
* Represents the status of history database.
*
* Bug 1965962 - Freeze const objects and use the specific values for types.
*
* @readonly
* @enum {number}
*/
const STATUS = { const STATUS = {
CLEAN: 0, CLEAN: 0,
DIRTY: 1, DIRTY: 1,
UNKNOWN: 2, UNKNOWN: 2,
}; };
// Represents actions on which a query will run. /**
* Represents actions on which a query will run.
*
* @readonly
* @enum {number}
*/
const ACTION = { const ACTION = {
TIMED: 1 << 0, // happens every this.intervalSeconds /** Happens every this.intervalSeconds. */
TIMED_OVERLIMIT: 1 << 1, // like TIMED but only when history is over limits TIMED: 1 << 0, //
SHUTDOWN_DIRTY: 1 << 2, // happens at shutdown for DIRTY state
IDLE_DIRTY: 1 << 3, // happens on idle for DIRTY state /** Like TIMED but only when history is over limits. */
IDLE_DAILY: 1 << 4, // happens once a day on idle TIMED_OVERLIMIT: 1 << 1,
DEBUG: 1 << 5, // happens on TOPIC_DEBUG_START_EXPIRATION
/** Happens at shutdown for DIRTY state. */
SHUTDOWN_DIRTY: 1 << 2,
/** Happens on idle for DIRTY state. */
IDLE_DIRTY: 1 << 3,
/** Happens once a day on idle. */
IDLE_DAILY: 1 << 4,
/** Happens on TOPIC_DEBUG_START_EXPIRATION. */
DEBUG: 1 << 5,
}; };
// The queries we use to expire. // The queries we use to expire.
@@ -576,6 +607,7 @@ nsPlacesExpiration.prototype = {
let expectedResults = row.getResultByName("expected_results"); let expectedResults = row.getResultByName("expected_results");
if (expectedResults > 0) { if (expectedResults > 0) {
if (!("_expectedResultsCount" in this)) { if (!("_expectedResultsCount" in this)) {
// @ts-ignore - Bug 1965966 this is dynamically created/deleted.
this._expectedResultsCount = expectedResults; this._expectedResultsCount = expectedResults;
} }
if (this._expectedResultsCount > 0) { if (this._expectedResultsCount > 0) {
@@ -592,7 +624,7 @@ nsPlacesExpiration.prototype = {
); );
if (mostRecentExpiredVisit) { if (mostRecentExpiredVisit) {
let days = parseInt( let days = Math.floor(
(Date.now() - mostRecentExpiredVisit / 1000) / MSECS_PER_DAY (Date.now() - mostRecentExpiredVisit / 1000) / MSECS_PER_DAY
); );
if (!this._mostRecentExpiredVisitDays) { if (!this._mostRecentExpiredVisitDays) {
@@ -618,6 +650,12 @@ nsPlacesExpiration.prototype = {
_shuttingDown: false, _shuttingDown: false,
_status: STATUS.UNKNOWN, _status: STATUS.UNKNOWN,
/**
* Set the status of the history database.
*
* @param {STATUS} aNewStatus
*/
set status(aNewStatus) { set status(aNewStatus) {
if (aNewStatus != this._status) { if (aNewStatus != this._status) {
// If status changes we should restart the timer. // If status changes we should restart the timer.
@@ -628,15 +666,30 @@ nsPlacesExpiration.prototype = {
this.expireOnIdle = aNewStatus == STATUS.DIRTY; this.expireOnIdle = aNewStatus == STATUS.DIRTY;
} }
}, },
/**
* Get the status of the history database.
*
* @returns {STATUS}
*/
get status() { get status() {
return this._status; return this._status;
}, },
/**
* Get the maximum number of pages that should be retained. This can expire
* old pages if a memory or disk threshold is exceeded.
*
* @returns {Promise<number>}
* The maximum number of pages.
*/
async getPagesLimit() { async getPagesLimit() {
if (this._pagesLimit != null) { if (this._pagesLimit != null) {
return this._pagesLimit; return this._pagesLimit;
} }
// @ts-ignore - maxPages is dynamically added.
if (this.maxPages >= 0) { if (this.maxPages >= 0) {
// @ts-ignore - maxPages is dynamically added.
return (this._pagesLimit = this.maxPages); return (this._pagesLimit = this.maxPages);
} }
@@ -646,6 +699,7 @@ nsPlacesExpiration.prototype = {
let memSizeBytes = MEMSIZE_FALLBACK_BYTES; let memSizeBytes = MEMSIZE_FALLBACK_BYTES;
try { try {
// Limit the size on systems with small memory. // Limit the size on systems with small memory.
// @ts-ignore - Typescript is not able to infer the type from nsIVariant
memSizeBytes = Services.sysinfo.getProperty("memsize"); memSizeBytes = Services.sysinfo.getProperty("memsize");
} catch (ex) {} } catch (ex) {}
if (memSizeBytes <= 0) { if (memSizeBytes <= 0) {
@@ -712,6 +766,15 @@ nsPlacesExpiration.prototype = {
_isIdleObserver: false, _isIdleObserver: false,
_expireOnIdle: false, _expireOnIdle: false,
/**
* Sets if expiration should occur when the system is idle. It also manages
* idle observation and adjusts behavior based on shutdown state and
* debugging.
*
* @param {boolean} aExpireOnIdle
* Whether to expire on idle time.
*/
set expireOnIdle(aExpireOnIdle) { set expireOnIdle(aExpireOnIdle) {
// Observe idle regardless aExpireOnIdle, since we always want to stop // Observe idle regardless aExpireOnIdle, since we always want to stop
// timed expiration on idle, to preserve mobile battery life. // timed expiration on idle, to preserve mobile battery life.
@@ -732,6 +795,12 @@ nsPlacesExpiration.prototype = {
this._expireOnIdle = aExpireOnIdle; this._expireOnIdle = aExpireOnIdle;
} }
}, },
/**
* Whether to expire visits and orphans.
*
* @returns {boolean}
*/
get expireOnIdle() { get expireOnIdle() {
return this._expireOnIdle; return this._expireOnIdle;
}, },
@@ -742,11 +811,10 @@ nsPlacesExpiration.prototype = {
/** /**
* Expires visits and orphans. * Expires visits and orphans.
* *
* @param aAction * @param {ACTION} aAction
* The ACTION we are expiring for. See the ACTION const for values. * The ACTION we are expiring for.
* @param aLimit * @param {LIMIT} aLimit
* Whether to use small, large or no limits when expiring. See the * Whether to use small, large or no limits when expiring.
* LIMIT const for values.
*/ */
async _expire(aAction, aLimit) { async _expire(aAction, aLimit) {
// Don't try to further expire after shutdown. // Don't try to further expire after shutdown.
@@ -827,13 +895,16 @@ nsPlacesExpiration.prototype = {
/** /**
* Generate a query used for expiration. * Generate a query used for expiration.
* *
* @param aQueryType * @param {string} aQueryType
* Type of the query. * Type of the query.
* @param aLimit * @param {LIMIT} aLimit
* Whether to use small, large or no limits when expiring. See the * Whether to use small, large or no limits when expiring.
* LIMIT const for values. * @param {ACTION} aAction
* @param aAction * Current action causing the expiration.
* Current action causing the expiration. See the ACTION const. *
* @returns {Promise<object|undefined>}
* Resolves an object based on the query, or undefined if no valid query
* type was provided.
*/ */
async _getQueryParams(aQueryType, aLimit, aAction) { async _getQueryParams(aQueryType, aLimit, aAction) {
let baseLimit; let baseLimit;
@@ -905,7 +976,9 @@ nsPlacesExpiration.prototype = {
/** /**
* Creates a new timer based on this.intervalSeconds. * Creates a new timer based on this.intervalSeconds.
* *
* @returns a REPEATING_SLACK nsITimer that runs every this.intervalSeconds. * @returns {nsITimer|undefined}
* A REPEATING_SLACK nsITimer that runs every this.intervalSeconds. Returns
* undefined if this is shutting down.
*/ */
_newTimer() { _newTimer() {
if (this._timer) { if (this._timer) {
@@ -916,14 +989,17 @@ nsPlacesExpiration.prototype = {
} }
if (!this._isIdleObserver) { if (!this._isIdleObserver) {
// @ts-ignore - _idle is lazily instantiated.
this._idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS); this._idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
this._isIdleObserver = true; this._isIdleObserver = true;
} }
// @ts-ignore - this.intervalSeconds is lazily instantiated.
let seconds = this.intervalSeconds;
let interval = let interval =
this.status != STATUS.DIRTY this.status != STATUS.DIRTY
? this.intervalSeconds * EXPIRE_AGGRESSIVITY_MULTIPLIER ? seconds * EXPIRE_AGGRESSIVITY_MULTIPLIER
: this.intervalSeconds; : seconds;
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback( timer.initWithCallback(