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

View File

@@ -87,33 +87,64 @@ const OVERLIMIT_PAGES_THRESHOLD = 1000;
// Milliseconds in a day.
const MSECS_PER_DAY = 86400000;
// When we expire we can use these limits:
// - SMALL for usual partial expirations, will expire a small chunk.
// - LARGE for idle or shutdown expirations, will expire a large chunk.
// - UNLIMITED will expire all the orphans.
// - DEBUG will use a known limit, passed along with the debug notification.
/**
* Represents the expiration limits.
*
* @readonly
* @enum {number}
*/
const LIMIT = {
/** SMALL for usual partial expirations, will expire a small chunk. */
SMALL: 0,
/** LARGE for idle or shutdown expirations, will expire a large chunk. */
LARGE: 1,
/** UNLIMITED will expire all the orphans. */
UNLIMITED: 2,
/** DEBUG will use a known limit, passed along with the debug notification. */
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 = {
CLEAN: 0,
DIRTY: 1,
UNKNOWN: 2,
};
// Represents actions on which a query will run.
/**
* Represents actions on which a query will run.
*
* @readonly
* @enum {number}
*/
const ACTION = {
TIMED: 1 << 0, // happens every this.intervalSeconds
TIMED_OVERLIMIT: 1 << 1, // like TIMED but only when history is over limits
SHUTDOWN_DIRTY: 1 << 2, // happens at shutdown for DIRTY state
IDLE_DIRTY: 1 << 3, // happens on idle for DIRTY state
IDLE_DAILY: 1 << 4, // happens once a day on idle
DEBUG: 1 << 5, // happens on TOPIC_DEBUG_START_EXPIRATION
/** Happens every this.intervalSeconds. */
TIMED: 1 << 0, //
/** Like TIMED but only when history is over limits. */
TIMED_OVERLIMIT: 1 << 1,
/** 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.
@@ -576,6 +607,7 @@ nsPlacesExpiration.prototype = {
let expectedResults = row.getResultByName("expected_results");
if (expectedResults > 0) {
if (!("_expectedResultsCount" in this)) {
// @ts-ignore - Bug 1965966 this is dynamically created/deleted.
this._expectedResultsCount = expectedResults;
}
if (this._expectedResultsCount > 0) {
@@ -592,7 +624,7 @@ nsPlacesExpiration.prototype = {
);
if (mostRecentExpiredVisit) {
let days = parseInt(
let days = Math.floor(
(Date.now() - mostRecentExpiredVisit / 1000) / MSECS_PER_DAY
);
if (!this._mostRecentExpiredVisitDays) {
@@ -618,6 +650,12 @@ nsPlacesExpiration.prototype = {
_shuttingDown: false,
_status: STATUS.UNKNOWN,
/**
* Set the status of the history database.
*
* @param {STATUS} aNewStatus
*/
set status(aNewStatus) {
if (aNewStatus != this._status) {
// If status changes we should restart the timer.
@@ -628,15 +666,30 @@ nsPlacesExpiration.prototype = {
this.expireOnIdle = aNewStatus == STATUS.DIRTY;
}
},
/**
* Get the status of the history database.
*
* @returns {STATUS}
*/
get 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() {
if (this._pagesLimit != null) {
return this._pagesLimit;
}
// @ts-ignore - maxPages is dynamically added.
if (this.maxPages >= 0) {
// @ts-ignore - maxPages is dynamically added.
return (this._pagesLimit = this.maxPages);
}
@@ -646,6 +699,7 @@ nsPlacesExpiration.prototype = {
let memSizeBytes = MEMSIZE_FALLBACK_BYTES;
try {
// 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");
} catch (ex) {}
if (memSizeBytes <= 0) {
@@ -712,6 +766,15 @@ nsPlacesExpiration.prototype = {
_isIdleObserver: 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) {
// Observe idle regardless aExpireOnIdle, since we always want to stop
// timed expiration on idle, to preserve mobile battery life.
@@ -732,6 +795,12 @@ nsPlacesExpiration.prototype = {
this._expireOnIdle = aExpireOnIdle;
}
},
/**
* Whether to expire visits and orphans.
*
* @returns {boolean}
*/
get expireOnIdle() {
return this._expireOnIdle;
},
@@ -742,11 +811,10 @@ nsPlacesExpiration.prototype = {
/**
* Expires visits and orphans.
*
* @param aAction
* The ACTION we are expiring for. See the ACTION const for values.
* @param aLimit
* Whether to use small, large or no limits when expiring. See the
* LIMIT const for values.
* @param {ACTION} aAction
* The ACTION we are expiring for.
* @param {LIMIT} aLimit
* Whether to use small, large or no limits when expiring.
*/
async _expire(aAction, aLimit) {
// Don't try to further expire after shutdown.
@@ -827,13 +895,16 @@ nsPlacesExpiration.prototype = {
/**
* Generate a query used for expiration.
*
* @param aQueryType
* Type of the query.
* @param aLimit
* Whether to use small, large or no limits when expiring. See the
* LIMIT const for values.
* @param aAction
* Current action causing the expiration. See the ACTION const.
* @param {string} aQueryType
* Type of the query.
* @param {LIMIT} aLimit
* Whether to use small, large or no limits when expiring.
* @param {ACTION} aAction
* Current action causing the expiration.
*
* @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) {
let baseLimit;
@@ -905,7 +976,9 @@ nsPlacesExpiration.prototype = {
/**
* 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() {
if (this._timer) {
@@ -916,14 +989,17 @@ nsPlacesExpiration.prototype = {
}
if (!this._isIdleObserver) {
// @ts-ignore - _idle is lazily instantiated.
this._idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
this._isIdleObserver = true;
}
// @ts-ignore - this.intervalSeconds is lazily instantiated.
let seconds = this.intervalSeconds;
let interval =
this.status != STATUS.DIRTY
? this.intervalSeconds * EXPIRE_AGGRESSIVITY_MULTIPLIER
: this.intervalSeconds;
? seconds * EXPIRE_AGGRESSIVITY_MULTIPLIER
: seconds;
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(