Bug 1953970 - Fix TypeScript issues raised in BookmarkHTMLUtils.sys.mjs and SyncedBookmarksMirror.sys.mjs. r=places-reviewers,daisuke.

This also changes Sqlite.sys.mjs so that TypeScript can use the OpenedConnection type from SyncedBookmarksMirror.sys.mjs.

Differential Revision: https://phabricator.services.mozilla.com/D241427
This commit is contained in:
Mark Banner
2025-03-25 11:26:34 +00:00
parent 8863389829
commit b9e65c78d5
3 changed files with 93 additions and 64 deletions

View File

@@ -91,6 +91,8 @@ function base64EncodeString(aString) {
/** /**
* Provides HTML escaping for use in HTML attributes and body of the bookmarks * Provides HTML escaping for use in HTML attributes and body of the bookmarks
* file, compatible with the old bookmarks system. * file, compatible with the old bookmarks system.
*
* @param {string} aText
*/ */
function escapeHtmlEntities(aText) { function escapeHtmlEntities(aText) {
return (aText || "") return (aText || "")
@@ -104,6 +106,8 @@ function escapeHtmlEntities(aText) {
/** /**
* Provides URL escaping for use in HTML attributes of the bookmarks file, * Provides URL escaping for use in HTML attributes of the bookmarks file,
* compatible with the old bookmarks system. * compatible with the old bookmarks system.
*
* @param {string} aText
*/ */
function escapeUrl(aText) { function escapeUrl(aText) {
return (aText || "").replace(/"/g, "%22"); return (aText || "").replace(/"/g, "%22");
@@ -121,21 +125,20 @@ export var BookmarkHTMLUtils = Object.freeze({
/** /**
* Loads the current bookmarks hierarchy from a "bookmarks.html" file. * Loads the current bookmarks hierarchy from a "bookmarks.html" file.
* *
* @param aSpec * @param {string} aSpec
* String containing the "file:" URI for the existing "bookmarks.html" * String containing the "file:" URI for the existing "bookmarks.html"
* file to be loaded. * file to be loaded.
* @param [options.replace] * @param {object} [options]
* @param {boolean} [options.replace]
* Whether we should erase existing bookmarks before loading. * Whether we should erase existing bookmarks before loading.
* Defaults to `false`. * Defaults to `false`.
* @param [options.source] * @param {number} [options.source]
* The bookmark change source, used to determine the sync status for * The bookmark change source, used to determine the sync status for
* imported bookmarks. Defaults to `RESTORE` if `replace = true`, or * imported bookmarks. Defaults to `RESTORE` if `replace = true`, or
* `IMPORT` otherwise. * `IMPORT` otherwise.
* *
* @returns {Promise<number>} The number of imported bookmarks, not including * @returns {Promise<number>} The number of imported bookmarks, not including
* folders and separators. * folders and separators. Rejects if there is an issue.
* @resolves When the new bookmarks have been created.
* @rejects JavaScript exception.
*/ */
async importFromURL( async importFromURL(
aSpec, aSpec,
@@ -170,20 +173,19 @@ export var BookmarkHTMLUtils = Object.freeze({
/** /**
* Loads the current bookmarks hierarchy from a "bookmarks.html" file. * Loads the current bookmarks hierarchy from a "bookmarks.html" file.
* *
* @param aFilePath * @param {string} aFilePath
* OS.File path string of the "bookmarks.html" file to be loaded. * OS.File path string of the "bookmarks.html" file to be loaded.
* @param [options.replace] * @param {object} options
* @param {boolean} [options.replace]
* Whether we should erase existing bookmarks before loading. * Whether we should erase existing bookmarks before loading.
* Defaults to `false`. * Defaults to `false`.
* @param [options.source] * @param {number} [options.source]
* The bookmark change source, used to determine the sync status for * The bookmark change source, used to determine the sync status for
* imported bookmarks. Defaults to `RESTORE` if `replace = true`, or * imported bookmarks. Defaults to `RESTORE` if `replace = true`, or
* `IMPORT` otherwise. * `IMPORT` otherwise.
* *
* @returns {Promise<number>} The number of imported bookmarks, not including * @returns {Promise<number>} The number of imported bookmarks, not including
* folders and separators * folders and separators. Rejects if there is an issue.
* @resolves When the new bookmarks have been created.
* @rejects JavaScript exception.
*/ */
async importFromFile( async importFromFile(
aFilePath, aFilePath,
@@ -225,12 +227,11 @@ export var BookmarkHTMLUtils = Object.freeze({
/** /**
* Saves the current bookmarks hierarchy to a "bookmarks.html" file. * Saves the current bookmarks hierarchy to a "bookmarks.html" file.
* *
* @param aFilePath * @param {string} aFilePath
* OS.File path string for the "bookmarks.html" file to be created. * OS.File path string for the "bookmarks.html" file to be created.
* *
* @returns {Promise} * @returns {Promise<number>} The exported bookmarks count. Rejects if there
* @resolves To the exported bookmarks count when the file has been created. * is an issue.
* @rejects JavaScript exception.
*/ */
async exportToFile(aFilePath) { async exportToFile(aFilePath) {
let [bookmarks, count] = await lazy.PlacesBackups.getBookmarksTree(); let [bookmarks, count] = await lazy.PlacesBackups.getBookmarksTree();
@@ -417,7 +418,7 @@ BookmarkImporter.prototype = {
/** /**
* Handles <hr> as a separator. * Handles <hr> as a separator.
* *
* @note Separators may have a title in old html files, though Places dropped * Separators may have a title in old html files, though Places dropped
* support for them. * support for them.
* We also don't import ADD_DATE or LAST_MODIFIED for separators because * We also don't import ADD_DATE or LAST_MODIFIED for separators because
* pre-Places bookmarks did not support them. * pre-Places bookmarks did not support them.
@@ -438,6 +439,8 @@ BookmarkImporter.prototype = {
* associated with the heading will be created when the tag has been closed * associated with the heading will be created when the tag has been closed
* and we know the title (we don't know to create a new folder or to merge * and we know the title (we don't know to create a new folder or to merge
* with an existing one until we have the title). * with an existing one until we have the title).
*
* @param {Element} aElt
*/ */
_handleHeadBegin: function handleHeadBegin(aElt) { _handleHeadBegin: function handleHeadBegin(aElt) {
let frame = this._curFrame; let frame = this._curFrame;
@@ -657,7 +660,7 @@ BookmarkImporter.prototype = {
this._curFrame.inDescription = true; this._curFrame.inDescription = true;
break; break;
case "hr": case "hr":
this._handleSeparator(aElt); this._handleSeparator();
break; break;
} }
}, },
@@ -707,12 +710,14 @@ BookmarkImporter.prototype = {
/** /**
* Converts a string date in seconds to a date object * Converts a string date in seconds to a date object
*
* @param {string} seconds
*/ */
_convertImportedDateToInternalDate: _convertImportedDateToInternalDate(seconds) {
function convertImportedDateToInternalDate(aDate) {
try { try {
if (aDate && !isNaN(aDate)) { let parsed = parseInt(seconds);
return new Date(parseInt(aDate) * 1000); // in bookmarks.html this value is in seconds if (!isNaN(parsed)) {
return new Date(parsed * 1000); // in bookmarks.html this value is in seconds
} }
} catch (ex) { } catch (ex) {
// Do nothing. // Do nothing.
@@ -794,9 +799,7 @@ BookmarkImporter.prototype = {
/** /**
* Imports the bookmarks from the importer into the places database. * Imports the bookmarks from the importer into the places database.
* *
* @param {BookmarkImporter} importer The importer from which to get the * @returns {Promise<number>} The number of imported bookmarks, not including
* bookmark information.
* @returns {number} The number of imported bookmarks, not including
* folders and separators * folders and separators
*/ */
async _importBookmarks() { async _importBookmarks() {
@@ -830,7 +833,7 @@ BookmarkImporter.prototype = {
* Imports data into the places database from the supplied url. * Imports data into the places database from the supplied url.
* *
* @param {string} href The url to import data from. * @param {string} href The url to import data from.
* @returns {number} The number of imported bookmarks, not including * @returns {Promise<number>} The number of imported bookmarks, not including
* folders and separators. * folders and separators.
*/ */
async importFromURL(href) { async importFromURL(href) {
@@ -923,6 +926,9 @@ BookmarkExporter.prototype = {
})(); })();
}, },
/**
* @type {?nsIConverterOutputStream}
*/
_converterOut: null, _converterOut: null,
_write(aText) { _write(aText) {

View File

@@ -47,6 +47,16 @@
* issues. * issues.
*/ */
// TODO: Import Barrier from AsyncShutdown.sys.mjs - needs modifications to
// make it easier for TypeScript.
// TODO: Import PlacesItem from bookmarks.sys.mjs - needs it setting up for
// TypeScript.
/**
* @typedef {any} Barrier
* @import {OpenedConnection} from "resource://gre/modules/Sqlite.sys.mjs"
* @typedef {any} PlacesItem
*/
const lazy = {}; const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, { ChromeUtils.defineESModuleGetters(lazy, {
@@ -115,6 +125,10 @@ class LogAdapter {
error(message) { error(message) {
this.log.error(message); this.log.error(message);
} }
info(message) {
this.log.info(message);
}
} }
/** /**
@@ -183,7 +197,7 @@ class ProgressTracker {
* Returns the shutdown blocker state. This is included in shutdown hang * Returns the shutdown blocker state. This is included in shutdown hang
* crash reports, in the `AsyncShutdownTimeout` annotation. * crash reports, in the `AsyncShutdownTimeout` annotation.
* *
* @see `fetchState` in `AsyncShutdown` for more details. * @see fetchState in `AsyncShutdown` for more details.
* @returns {object} A stringifiable object with the recorded steps. * @returns {object} A stringifiable object with the recorded steps.
*/ */
fetchState() { fetchState() {
@@ -245,7 +259,7 @@ export class SyncedBookmarksMirror {
recordStepTelemetry, recordStepTelemetry,
recordValidationTelemetry, recordValidationTelemetry,
finalizeAt = lazy.PlacesUtils.history.shutdownClient.jsclient, finalizeAt = lazy.PlacesUtils.history.shutdownClient.jsclient,
} = {} }
) { ) {
this.db = db; this.db = db;
this.wasCorrupt = wasCorrupt; this.wasCorrupt = wasCorrupt;
@@ -275,6 +289,7 @@ export class SyncedBookmarksMirror {
* newest schema version. Automatically recreates the mirror if it's corrupt; * newest schema version. Automatically recreates the mirror if it's corrupt;
* throws on failure. * throws on failure.
* *
* @param {object} options
* @param {string} options.path * @param {string} options.path
* The path to the mirror database file, either absolute or relative * The path to the mirror database file, either absolute or relative
* to the profile path. * to the profile path.
@@ -289,12 +304,11 @@ export class SyncedBookmarksMirror {
* problems: Array)`, where `took` is the time taken to run * problems: Array)`, where `took` is the time taken to run
* validation in milliseconds, `checked` is the number of items * validation in milliseconds, `checked` is the number of items
* checked, and `problems` is an array of named problem counts. * checked, and `problems` is an array of named problem counts.
* @param {AsyncShutdown.Barrier} [options.finalizeAt] * @param {Barrier} [options.finalizeAt]
* A shutdown phase, barrier, or barrier client that should * A shutdown phase, barrier, or barrier client that should
* automatically finalize the mirror when triggered. Exposed for * automatically finalize the mirror when triggered. Exposed for
* testing. * testing.
* @returns {SyncedBookmarksMirror} * @returns {Promise<SyncedBookmarksMirror>}
* A mirror ready for use.
*/ */
static async open(options) { static async open(options) {
let db = await lazy.PlacesUtils.promiseUnsafeWritableDBConnection(); let db = await lazy.PlacesUtils.promiseUnsafeWritableDBConnection();
@@ -337,7 +351,7 @@ export class SyncedBookmarksMirror {
* timestamp as the "high water mark" for all downloaded records. Each sync * timestamp as the "high water mark" for all downloaded records. Each sync
* downloads and stores records that are strictly newer than this time. * downloads and stores records that are strictly newer than this time.
* *
* @returns {number} * @returns {Promise<number>}
* The high water mark time, in seconds. * The high water mark time, in seconds.
*/ */
async getCollectionHighWaterMark() { async getCollectionHighWaterMark() {
@@ -367,7 +381,9 @@ export class SyncedBookmarksMirror {
* The collection last modified time, in seconds. * The collection last modified time, in seconds.
*/ */
async setCollectionLastModified(lastModifiedSeconds) { async setCollectionLastModified(lastModifiedSeconds) {
let lastModified = Math.floor(lastModifiedSeconds * 1000); let lastModified = Math.floor(
/** @type {number} */ (lastModifiedSeconds) * 1000
);
if (!Number.isInteger(lastModified)) { if (!Number.isInteger(lastModified)) {
throw new TypeError("Invalid collection last modified time"); throw new TypeError("Invalid collection last modified time");
} }
@@ -390,7 +406,7 @@ export class SyncedBookmarksMirror {
* Returns the bookmarks collection sync ID. This corresponds to * Returns the bookmarks collection sync ID. This corresponds to
* `PlacesSyncUtils.bookmarks.getSyncId`. * `PlacesSyncUtils.bookmarks.getSyncId`.
* *
* @returns {string} * @returns {Promise<string>}
* The sync ID, or `""` if one isn't set. * The sync ID, or `""` if one isn't set.
*/ */
async getSyncId() { async getSyncId() {
@@ -451,6 +467,7 @@ export class SyncedBookmarksMirror {
* *
* @param {PlacesItem[]} records * @param {PlacesItem[]} records
* Sync records to store in the mirror. * Sync records to store in the mirror.
* @param {object} [options]
* @param {boolean} [options.needsMerge] * @param {boolean} [options.needsMerge]
* Indicates if the records were changed remotely since the last sync, * Indicates if the records were changed remotely since the last sync,
* and should be merged into the local tree. This option is set to * and should be merged into the local tree. This option is set to
@@ -528,6 +545,7 @@ export class SyncedBookmarksMirror {
* on `SQLITE_BUSY`; synchronous consumers will fail after waiting for 100ms. * on `SQLITE_BUSY`; synchronous consumers will fail after waiting for 100ms.
* See bug 1305563, comment 122 for details. * See bug 1305563, comment 122 for details.
* *
* @param {object} [options]
* @param {number} [options.localTimeSeconds] * @param {number} [options.localTimeSeconds]
* The current local time, in seconds. * The current local time, in seconds.
* @param {number} [options.remoteTimeSeconds] * @param {number} [options.remoteTimeSeconds]
@@ -541,7 +559,7 @@ export class SyncedBookmarksMirror {
* An abort signal that can be used to interrupt a merge when its * An abort signal that can be used to interrupt a merge when its
* associated `AbortController` is aborted. If omitted, the merge can * associated `AbortController` is aborted. If omitted, the merge can
* still be interrupted when the mirror is finalized. * still be interrupted when the mirror is finalized.
* @returns {Object.<string, BookmarkChangeRecord>} * @returns {Promise<{ [x: string]: BookmarkChangeRecord; }>}
* A changeset containing locally changed and reconciled records to * A changeset containing locally changed and reconciled records to
* upload to the server, and to store in the mirror once upload * upload to the server, and to store in the mirror once upload
* succeeds. * succeeds.
@@ -774,7 +792,7 @@ export class SyncedBookmarksMirror {
* Fetches the GUIDs of all items in the remote tree that need to be merged * Fetches the GUIDs of all items in the remote tree that need to be merged
* into the local tree. * into the local tree.
* *
* @returns {string[]} * @returns {Promise<string[]>}
* Remotely changed GUIDs that need to be merged into Places. * Remotely changed GUIDs that need to be merged into Places.
*/ */
async fetchUnmergedGuids() { async fetchUnmergedGuids() {
@@ -1145,7 +1163,7 @@ export class SyncedBookmarksMirror {
* @param {AbortSignal} signal * @param {AbortSignal} signal
* Stops fetching records when the associated `AbortController` * Stops fetching records when the associated `AbortController`
* is aborted. * is aborted.
* @returns {object} * @returns {Promise<{ changeRecords: any; count: number }>}
* A `{ changeRecords, count }` tuple, where `changeRecords` is a * A `{ changeRecords, count }` tuple, where `changeRecords` is a
* changeset containing Sync record cleartexts for outgoing items and * changeset containing Sync record cleartexts for outgoing items and
* tombstones, keyed by their Sync record IDs, and `count` is the * tombstones, keyed by their Sync record IDs, and `count` is the
@@ -1395,6 +1413,7 @@ export class SyncedBookmarksMirror {
* shutdown, but may also be called explicitly when the mirror is no longer * shutdown, but may also be called explicitly when the mirror is no longer
* needed. * needed.
* *
* @param {object} options
* @param {boolean} [options.alsoCleanup] * @param {boolean} [options.alsoCleanup]
* If specified, drop all temp tables, views, and triggers, * If specified, drop all temp tables, views, and triggers,
* and detach from the mirror database before closing the * and detach from the mirror database before closing the
@@ -1481,10 +1500,10 @@ function isDatabaseCorrupt(error) {
} }
if (error.errors) { if (error.errors) {
return error.errors.some( return error.errors.some(
error => e =>
error instanceof Ci.mozIStorageError && e instanceof Ci.mozIStorageError &&
(error.result == Ci.mozIStorageError.CORRUPT || (e.result == Ci.mozIStorageError.CORRUPT ||
error.result == Ci.mozIStorageError.NOTADB) e.result == Ci.mozIStorageError.NOTADB)
); );
} }
return false; return false;
@@ -1495,7 +1514,7 @@ function isDatabaseCorrupt(error) {
* migrates the mirror schema to the latest version, and creates temporary * migrates the mirror schema to the latest version, and creates temporary
* tables, views, and triggers. * tables, views, and triggers.
* *
* @param {Sqlite.OpenedConnection} db * @param {OpenedConnection} db
* The Places database connection. * The Places database connection.
* @param {string} path * @param {string} path
* The full path to the mirror database file. * The full path to the mirror database file.
@@ -1527,7 +1546,7 @@ async function attachAndInitMirrorDatabase(db, path) {
/** /**
* Migrates the mirror database schema to the latest version. * Migrates the mirror database schema to the latest version.
* *
* @param {Sqlite.OpenedConnection} db * @param {OpenedConnection} db
* The mirror database connection. * The mirror database connection.
* @param {number} currentSchemaVersion * @param {number} currentSchemaVersion
* The current mirror database schema version. * The current mirror database schema version.
@@ -1577,7 +1596,7 @@ async function migrateMirrorSchema(db, currentSchemaVersion) {
* Initializes a new mirror database, creating persistent tables, indexes, and * Initializes a new mirror database, creating persistent tables, indexes, and
* roots. * roots.
* *
* @param {Sqlite.OpenedConnection} db * @param {OpenedConnection} db
* The mirror database connection. * The mirror database connection.
*/ */
async function initializeMirrorDatabase(db) { async function initializeMirrorDatabase(db) {
@@ -1655,7 +1674,7 @@ async function initializeMirrorDatabase(db) {
* Drops all temp tables, views, and triggers used for merging, and detaches * Drops all temp tables, views, and triggers used for merging, and detaches
* from the mirror database. * from the mirror database.
* *
* @param {Sqlite.OpenedConnection} db * @param {OpenedConnection} db
* The mirror database connection. * The mirror database connection.
*/ */
async function cleanupMirrorDatabase(db) { async function cleanupMirrorDatabase(db) {
@@ -1680,7 +1699,7 @@ async function cleanupMirrorDatabase(db) {
* from these roots - however, malformed records from the server which create * from these roots - however, malformed records from the server which create
* a different root *will* be created in the mirror - just not applied. * a different root *will* be created in the mirror - just not applied.
* *
* @param {Sqlite.OpenedConnection} db * @param {OpenedConnection} db
* The mirror database connection. * The mirror database connection.
*/ */
async function createMirrorRoots(db) { async function createMirrorRoots(db) {
@@ -1728,7 +1747,7 @@ async function createMirrorRoots(db) {
/** /**
* Creates temporary tables, views, and triggers to apply the mirror to Places. * Creates temporary tables, views, and triggers to apply the mirror to Places.
* *
* @param {Sqlite.OpenedConnection} db * @param {OpenedConnection} db
* The mirror database connection. * The mirror database connection.
*/ */
async function initializeTempMirrorEntities(db) { async function initializeTempMirrorEntities(db) {
@@ -2094,7 +2113,7 @@ function validateTag(rawTag) {
* @param {Function} [recordTiming] * @param {Function} [recordTiming]
* An optional function with the signature `(time: Number)`, where * An optional function with the signature `(time: Number)`, where
* `time` is the measured time. * `time` is the measured time.
* @returns The return value of the timed function. * @returns {Promise<any>} The return value of the timed function.
*/ */
async function withTiming(name, func, recordTiming) { async function withTiming(name, func, recordTiming) {
lazy.MirrorLog.debug(name); lazy.MirrorLog.debug(name);
@@ -2572,7 +2591,7 @@ function bagToNamedCounts(bag, names) {
* cancellations. * cancellations.
* *
* @param {AbortSignal} finalizeSignal * @param {AbortSignal} finalizeSignal
* @param {AbortSignal?} signal * @param {AbortSignal?} interruptSignal
* @returns {AbortSignal} * @returns {AbortSignal}
*/ */
function anyAborted(finalizeSignal, interruptSignal = null) { function anyAborted(finalizeSignal, interruptSignal = null) {

View File

@@ -1661,7 +1661,7 @@ function wrapStorageConnection(options) {
* (object) Options to control behavior of connection. See * (object) Options to control behavior of connection. See
* `openConnection`. * `openConnection`.
*/ */
function OpenedConnection(connection, identifier, options = {}) { export function OpenedConnection(connection, identifier, options = {}) {
// Store all connection data in a field distinct from the // Store all connection data in a field distinct from the
// witness. This enables us to store an additional reference to this // witness. This enables us to store an additional reference to this
// field without preventing garbage collection of // field without preventing garbage collection of
@@ -1697,7 +1697,7 @@ function convertStorageTransactionType(type) {
return OpenedConnection.TRANSACTION_TYPES[type]; return OpenedConnection.TRANSACTION_TYPES[type];
} }
OpenedConnection.prototype = Object.freeze({ OpenedConnection.prototype = {
TRANSACTION_DEFAULT: "DEFAULT", TRANSACTION_DEFAULT: "DEFAULT",
TRANSACTION_DEFERRED: "DEFERRED", TRANSACTION_DEFERRED: "DEFERRED",
TRANSACTION_IMMEDIATE: "IMMEDIATE", TRANSACTION_IMMEDIATE: "IMMEDIATE",
@@ -2058,7 +2058,11 @@ OpenedConnection.prototype = Object.freeze({
stepDelayMs stepDelayMs
); );
}, },
}); };
// This is frozen after the prototype has been assigned to allow TypeScript
// identify the properties in the prototype. Ideally we'd change this to be a
// class definition.
OpenedConnection.prototype = Object.freeze(OpenedConnection.prototype);
export var Sqlite = { export var Sqlite = {
// The maximum time to wait before considering a transaction stuck and // The maximum time to wait before considering a transaction stuck and