diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:14:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:14:29 +0000 |
commit | fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8 (patch) | |
tree | 4c1ccaf5486d4f2009f9a338a98a83e886e29c97 /toolkit/modules | |
parent | Releasing progress-linux version 124.0.1-1~progress7.99u1. (diff) | |
download | firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.tar.xz firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.zip |
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/modules')
28 files changed, 326 insertions, 338 deletions
diff --git a/toolkit/modules/ActorManagerParent.sys.mjs b/toolkit/modules/ActorManagerParent.sys.mjs index 3bf7299abe..5b31421ec6 100644 --- a/toolkit/modules/ActorManagerParent.sys.mjs +++ b/toolkit/modules/ActorManagerParent.sys.mjs @@ -317,6 +317,17 @@ let JSWINDOWACTORS = { child: { esModuleURI: "resource://gre/actors/FormHistoryChild.sys.mjs", events: { + "form-submission-detected": {}, + }, + }, + + allFrames: true, + }, + + FormHandler: { + child: { + esModuleURI: "resource://gre/actors/FormHandlerChild.sys.mjs", + events: { DOMFormBeforeSubmit: {}, }, }, @@ -355,7 +366,7 @@ let JSWINDOWACTORS = { child: { esModuleURI: "resource://gre/modules/LoginManagerChild.sys.mjs", events: { - DOMFormBeforeSubmit: {}, + "form-submission-detected": {}, DOMFormHasPassword: {}, DOMFormHasPossibleUsername: {}, DOMInputPasswordAdded: {}, @@ -372,6 +383,22 @@ let JSWINDOWACTORS = { }, }, + // A single process (shared with translations) that manages machine learning engines. + MLEngine: { + parent: { + esModuleURI: "resource://gre/actors/MLEngineParent.sys.mjs", + }, + child: { + esModuleURI: "resource://gre/actors/MLEngineChild.sys.mjs", + events: { + DOMContentLoaded: { createActor: true }, + }, + }, + includeChrome: true, + matches: ["chrome://global/content/ml/MLEngine.html"], + enablePreference: "browser.ml.enable", + }, + NetError: { parent: { esModuleURI: "resource://gre/actors/NetErrorParent.sys.mjs", diff --git a/toolkit/modules/AppConstants.sys.mjs b/toolkit/modules/AppConstants.sys.mjs index 36b26056ec..bfc87fa622 100644 --- a/toolkit/modules/AppConstants.sys.mjs +++ b/toolkit/modules/AppConstants.sys.mjs @@ -5,10 +5,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -const lazy = {}; -ChromeUtils.defineModuleGetter(lazy, "AddonManager", "resource://gre/modules/AddonManager.jsm"); - -// Immutable for export. +/** + * AppConstants is a set of immutable constants that are defined at build time. + * These should not depend on any other JavaScript module. + */ export var AppConstants = Object.freeze({ // See this wiki page for more details about channel specific build // defines: https://wiki.mozilla.org/Platform/Channel-specific_build_defines @@ -292,16 +292,19 @@ export var AppConstants = Object.freeze({ false, #endif - get MOZ_UNSIGNED_SCOPES() { - let result = 0; + MOZ_UNSIGNED_APP_SCOPE: #ifdef MOZ_UNSIGNED_APP_SCOPE - result |= lazy.AddonManager.SCOPE_APPLICATION; + true, +#else + false, #endif + + MOZ_UNSIGNED_SYSTEM_SCOPE: #ifdef MOZ_UNSIGNED_SYSTEM_SCOPE - result |= lazy.AddonManager.SCOPE_SYSTEM; + true, +#else + false, #endif - return result; - }, MOZ_ALLOW_ADDON_SIDELOAD: #ifdef MOZ_ALLOW_ADDON_SIDELOAD diff --git a/toolkit/modules/BrowserUtils.sys.mjs b/toolkit/modules/BrowserUtils.sys.mjs index 1963b9728e..db9ef425c5 100644 --- a/toolkit/modules/BrowserUtils.sys.mjs +++ b/toolkit/modules/BrowserUtils.sys.mjs @@ -112,6 +112,7 @@ export var BrowserUtils = { canFindInPage(location) { return ( !location.startsWith("about:preferences") && + !location.startsWith("about:settings") && !location.startsWith("about:logins") && !(location.startsWith("about:firefoxview") && lazy.FXVIEW_SEARCH_ENABLED) ); diff --git a/toolkit/modules/ClipboardContextMenu.sys.mjs b/toolkit/modules/ClipboardContextMenu.sys.mjs index 011bfe64d7..d66a2f466d 100644 --- a/toolkit/modules/ClipboardContextMenu.sys.mjs +++ b/toolkit/modules/ClipboardContextMenu.sys.mjs @@ -182,12 +182,18 @@ export var ClipboardContextMenu = { _startWatchingForSpammyActivation() { let doc = this._menuitem.ownerDocument; - Services.els.addSystemEventListener(doc, "keydown", this, true); + doc.addEventListener("keydown", this, { + capture: true, + mozSystemGroup: true, + }); }, _stopWatchingForSpammyActivation() { let doc = this._menuitem.ownerDocument; - Services.els.removeSystemEventListener(doc, "keydown", this, true); + doc.removeEventListener("keydown", this, { + capture: true, + mozSystemGroup: true, + }); }, _delayTimer: null, diff --git a/toolkit/modules/Console.sys.mjs b/toolkit/modules/Console.sys.mjs index 5fb4f750f4..c3964fa840 100644 --- a/toolkit/modules/Console.sys.mjs +++ b/toolkit/modules/Console.sys.mjs @@ -6,7 +6,7 @@ * Define a 'console' API to roughly match the implementation provided by * Firebug. * This module helps cases where code is shared between the web and Firefox. - * See also Browser.jsm for an implementation of other web constants to help + * See also Browser.sys.mjs for an implementation of other web constants to help * sharing code between the web and firefox; * * The API is only be a rough approximation for 3 reasons: diff --git a/toolkit/modules/DateTimePickerPanel.sys.mjs b/toolkit/modules/DateTimePickerPanel.sys.mjs index 8d67cb0d8e..f20f2c1668 100644 --- a/toolkit/modules/DateTimePickerPanel.sys.mjs +++ b/toolkit/modules/DateTimePickerPanel.sys.mjs @@ -68,8 +68,6 @@ export var DateTimePickerPanel = class { closePicker(clear) { if (clear) { this.element.dispatchEvent(new CustomEvent("DateTimePickerValueCleared")); - } else { - this.setInputBoxValue(true); } this.pickerState = {}; this.type = undefined; diff --git a/toolkit/modules/Deprecated.sys.mjs b/toolkit/modules/Deprecated.sys.mjs deleted file mode 100644 index c8f5aeba01..0000000000 --- a/toolkit/modules/Deprecated.sys.mjs +++ /dev/null @@ -1,81 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const PREF_DEPRECATION_WARNINGS = "devtools.errorconsole.deprecation_warnings"; - -// A flag that indicates whether deprecation warnings should be logged. -var logWarnings = Services.prefs.getBoolPref(PREF_DEPRECATION_WARNINGS); - -Services.prefs.addObserver( - PREF_DEPRECATION_WARNINGS, - function (aSubject, aTopic, aData) { - logWarnings = Services.prefs.getBoolPref(PREF_DEPRECATION_WARNINGS); - } -); - -/** - * Build a callstack log message. - * - * @param nsIStackFrame aStack - * A callstack to be converted into a string log message. - */ -function stringifyCallstack(aStack) { - // If aStack is invalid, use Components.stack (ignoring the last frame). - if (!aStack || !(aStack instanceof Ci.nsIStackFrame)) { - aStack = Components.stack.caller; - } - - let frame = aStack.caller; - let msg = ""; - // Get every frame in the callstack. - while (frame) { - if (frame.filename || frame.lineNumber || frame.name) { - msg += frame.filename + " " + frame.lineNumber + " " + frame.name + "\n"; - } - frame = frame.caller; - } - return msg; -} - -export var Deprecated = { - /** - * Log a deprecation warning. - * - * @param string aText - * Deprecation warning text. - * @param string aUrl - * A URL pointing to documentation describing deprecation - * and the way to address it. - * @param nsIStackFrame aStack - * An optional callstack. If it is not provided a - * snapshot of the current JavaScript callstack will be - * logged. - */ - warning(aText, aUrl, aStack) { - if (!logWarnings) { - return; - } - - // If URL is not provided, report an error. - if (!aUrl) { - console.error( - "Error in Deprecated.warning: warnings must " + - "provide a URL documenting this deprecation." - ); - return; - } - - let textMessage = - "DEPRECATION WARNING: " + - aText + - "\nYou may find more details about this deprecation at: " + - aUrl + - "\n" + - // Append a callstack part to the deprecation message. - stringifyCallstack(aStack); - - // Report deprecation warning. - console.error(textMessage); - }, -}; diff --git a/toolkit/modules/FindBarContent.sys.mjs b/toolkit/modules/FindBarContent.sys.mjs index 8b34d93f9d..ef3a197d50 100644 --- a/toolkit/modules/FindBarContent.sys.mjs +++ b/toolkit/modules/FindBarContent.sys.mjs @@ -27,12 +27,9 @@ export class FindBarContent { startQuickFind(event, autostart = false) { if (!this.addedEventListener) { this.addedEventListener = true; - Services.els.addSystemEventListener( - this.actor.document.defaultView, - "mouseup", - this, - false - ); + this.actor.document.defaultView.addEventListener("mouseup", this, { + mozSystemGroup: true, + }); } let mode = FIND_TYPEAHEAD; diff --git a/toolkit/modules/FirstStartup.sys.mjs b/toolkit/modules/FirstStartup.sys.mjs index b31e7ffa07..c09885abe9 100644 --- a/toolkit/modules/FirstStartup.sys.mjs +++ b/toolkit/modules/FirstStartup.sys.mjs @@ -36,8 +36,25 @@ export var FirstStartup = { * completed, or until a timeout is reached. * * In the latter case, services are expected to run post-UI instead as usual. + * + * @param {boolean} newProfile + * True if a new profile was just created, false otherwise. */ - init() { + init(newProfile) { + if (!newProfile) { + // In this case, we actually don't want to do any FirstStartup work, + // since a pre-existing profile was detected (presumably, we entered here + // because a user re-installed via the stub installer when there existed + // previous user profiles on the file system). We do, however, want to + // measure how often this occurs. + Glean.firstStartup.statusCode.set(this.NOT_STARTED); + Glean.firstStartup.newProfile.set(false); + GleanPings.firstStartup.submit(); + return; + } + + Glean.firstStartup.newProfile.set(true); + this._state = this.IN_PROGRESS; const timeout = Services.prefs.getIntPref(PREF_TIMEOUT, 30000); // default to 30 seconds let startingTime = Cu.now(); @@ -106,4 +123,11 @@ export var FirstStartup = { get state() { return this._state; }, + + /** + * For testing only. This puts us back into the initial NOT_STARTED state. + */ + resetForTesting() { + this._state = this.NOT_STARTED; + }, }; diff --git a/toolkit/modules/ObjectUtils.sys.mjs b/toolkit/modules/ObjectUtils.sys.mjs index e0fbeead12..2c927477b7 100644 --- a/toolkit/modules/ObjectUtils.sys.mjs +++ b/toolkit/modules/ObjectUtils.sys.mjs @@ -142,6 +142,22 @@ function objEquiv(a, b) { if ((a.prototype || undefined) != (b.prototype || undefined)) { return false; } + + // Check for ArrayBuffer equality + if (instanceOf(a, "ArrayBuffer") && instanceOf(b, "ArrayBuffer")) { + if (a.byteLength !== b.byteLength) { + return false; + } + const viewA = new Uint8Array(a); + const viewB = new Uint8Array(b); + for (let i = 0; i < viewA.length; i++) { + if (viewA[i] !== viewB[i]) { + return false; + } + } + return true; + } + // Object.keys may be broken through screwy arguments passing. Converting to // an array solves the problem. if (isArguments(a)) { diff --git a/toolkit/modules/ProcessType.sys.mjs b/toolkit/modules/ProcessType.sys.mjs index 4c30068755..f8dcfb6fae 100644 --- a/toolkit/modules/ProcessType.sys.mjs +++ b/toolkit/modules/ProcessType.sys.mjs @@ -15,6 +15,17 @@ export const ProcessType = Object.freeze({ socket: "process-type-socket", utility: "process-type-utility", + // Utility with actor names + utility_audioDecoder_Generic: + "process-type-utility-actor-audio-decoder-generic", + utility_audioDecoder_AppleMedia: + "process-type-utility-actor-audio-decoder-applemedia", + utility_audioDecoder_WMF: "process-type-utility-actor-audio-decoder-wmf", + utility_mfMediaEngineCDM: "process-type-utility-actor-mf-media-engine", + utility_jSOracle: "process-type-utility-actor-js-oracle", + utility_windowsUtils: "process-type-utility-actor-windows-utils", + utility_windowsFileDialog: "process-type-utility-actor-windows-file-dialog", + // Keys defined in dom/ipc/RemoteType.h extension: "process-type-extension", file: "process-type-file", diff --git a/toolkit/modules/RemotePageAccessManager.sys.mjs b/toolkit/modules/RemotePageAccessManager.sys.mjs index d2ab2fb805..61c00880cb 100644 --- a/toolkit/modules/RemotePageAccessManager.sys.mjs +++ b/toolkit/modules/RemotePageAccessManager.sys.mjs @@ -89,7 +89,10 @@ export let RemotePageAccessManager = { "OpenTRRPreferences", ], RPMCheckAlternateHostAvailable: ["*"], - RPMRecordTelemetryEvent: ["security.doh.neterror"], + RPMRecordTelemetryEvent: [ + "security.doh.neterror", + "security.ui.tlserror", + ], RPMAddMessageListener: ["*"], RPMRemoveMessageListener: ["*"], RPMGetFormatURLPref: [ diff --git a/toolkit/modules/Sqlite.sys.mjs b/toolkit/modules/Sqlite.sys.mjs index 2c9de0f438..b1f48c28be 100644 --- a/toolkit/modules/Sqlite.sys.mjs +++ b/toolkit/modules/Sqlite.sys.mjs @@ -23,11 +23,14 @@ import { setTimeout } from "resource://gre/modules/Timer.sys.mjs"; const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", - FileUtils: "resource://gre/modules/FileUtils.sys.mjs", - Log: "resource://gre/modules/Log.sys.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", + }, + { global: "contextual" } +); XPCOMUtils.defineLazyServiceGetter( lazy, @@ -307,6 +310,18 @@ function unregisterVacuumParticipant(connectionData) { } /** + * Create a ConsoleInstance logger with a given prefix. + * @param {string} prefix The prefix to use when logging. + * @returns {ConsoleInstance} a console logger. + */ +function createLoggerWithPrefix(prefix) { + return console.createInstance({ + prefix: `SQLite JSM (${prefix})`, + maxLogLevelPref: "toolkit.sqlitejsm.loglevel", + }); +} + +/** * Connection data with methods necessary for closing the connection. * * To support auto-closing in the event of garbage collection, this @@ -325,12 +340,8 @@ function unregisterVacuumParticipant(connectionData) { * dispatch its method calls here. */ function ConnectionData(connection, identifier, options = {}) { - this._log = lazy.Log.repository.getLoggerWithMessagePrefix( - "Sqlite.sys.mjs", - `Connection ${identifier}: ` - ); - this._log.manageLevelFromPref("toolkit.sqlitejsm.loglevel"); - this._log.debug("Opened"); + this._logger = createLoggerWithPrefix(`Connection ${identifier}`); + this._logger.debug("Opened"); this._dbConn = connection; @@ -410,25 +421,27 @@ function ConnectionData(connection, identifier, options = {}) { this._useIncrementalVacuum = !!options.incrementalVacuum; if (this._useIncrementalVacuum) { - this._log.debug("Set auto_vacuum INCREMENTAL"); + this._logger.debug("Set auto_vacuum INCREMENTAL"); this.execute("PRAGMA auto_vacuum = 2").catch(ex => { - this._log.error("Setting auto_vacuum to INCREMENTAL failed."); + this._logger.error("Setting auto_vacuum to INCREMENTAL failed."); console.error(ex); }); } this._expectedPageSize = options.pageSize ?? 0; if (this._expectedPageSize) { - this._log.debug("Set page_size to " + this._expectedPageSize); + this._logger.debug("Set page_size to " + this._expectedPageSize); this.execute("PRAGMA page_size = " + this._expectedPageSize).catch(ex => { - this._log.error(`Setting page_size to ${this._expectedPageSize} failed.`); + this._logger.error( + `Setting page_size to ${this._expectedPageSize} failed.` + ); console.error(ex); }); } this._vacuumOnIdle = options.vacuumOnIdle; if (this._vacuumOnIdle) { - this._log.debug("Register as vacuum participant"); + this._logger.debug("Register as vacuum participant"); this.QueryInterface = ChromeUtils.generateQI([ Ci.mozIStorageVacuumParticipant, ]); @@ -470,12 +483,12 @@ ConnectionData.prototype = Object.freeze({ onBeginVacuum() { let granted = !this.transactionInProgress; - this._log.debug("Begin Vacuum - " + granted ? "granted" : "denied"); + this._logger.debug("Begin Vacuum - " + granted ? "granted" : "denied"); return granted; }, onEndVacuum(succeeded) { - this._log.debug("End Vacuum - " + succeeded ? "success" : "failure"); + this._logger.debug("End Vacuum - " + succeeded ? "success" : "failure"); }, /** @@ -597,11 +610,11 @@ ConnectionData.prototype = Object.freeze({ return this._deferredClose.promise; } - this._log.debug("Request to close connection."); + this._logger.debug("Request to close connection."); this._clearIdleShrinkTimer(); if (this._vacuumOnIdle) { - this._log.debug("Unregister as vacuum participant"); + this._logger.debug("Unregister as vacuum participant"); unregisterVacuumParticipant(this); } @@ -616,7 +629,7 @@ ConnectionData.prototype = Object.freeze({ clone(readOnly = false) { this.ensureOpen(); - this._log.debug("Request to clone connection."); + this._logger.debug("Request to clone connection."); let options = { connection: this._dbConn, @@ -632,7 +645,7 @@ ConnectionData.prototype = Object.freeze({ return this._operationsCounter++; }, _finalize() { - this._log.debug("Finalizing connection."); + this._logger.debug("Finalizing connection."); // Cancel any pending statements. for (let [, /* k */ statement] of this._pendingStatements) { statement.cancel(); @@ -660,7 +673,7 @@ ConnectionData.prototype = Object.freeze({ // We must always close the connection at the Sqlite.sys.mjs-level, not // necessarily at the mozStorage-level. let markAsClosed = () => { - this._log.debug("Closed"); + this._logger.debug("Closed"); // Now that the connection is closed, no need to keep // a blocker for Barriers.connections. lazy.Barriers.connections.client.removeBlocker( @@ -673,7 +686,7 @@ ConnectionData.prototype = Object.freeze({ this._dbConn = null; markAsClosed(); } else { - this._log.debug("Calling asyncClose()."); + this._logger.debug("Calling asyncClose()."); try { this._dbConn.asyncClose(markAsClosed); } catch (ex) { @@ -770,7 +783,7 @@ ConnectionData.prototype = Object.freeze({ .pop() .match(/^([^@]*@).*\/([^\/:]+)[:0-9]*$/); caller = caller[1] + caller[2]; - this._log.debug(`Transaction (type ${type}) requested by: ${caller}`); + this._logger.debug(`Transaction (type ${type}) requested by: ${caller}`); if (type == OpenedConnection.prototype.TRANSACTION_DEFAULT) { type = this.defaultTransactionType; @@ -794,7 +807,7 @@ ConnectionData.prototype = Object.freeze({ // At this point we should never have an in progress transaction, since // they are enqueued. if (this._initiatedTransaction) { - this._log.error( + this._logger.error( "Unexpected transaction in progress when trying to start a new one." ); } @@ -802,7 +815,7 @@ ConnectionData.prototype = Object.freeze({ // We catch errors in statement execution to detect nested transactions. try { await this.execute("BEGIN " + type + " TRANSACTION"); - this._log.debug(`Begin transaction`); + this._logger.debug(`Begin transaction`); this._initiatedTransaction = true; } catch (ex) { // Unfortunately, if we are wrapping an existing connection, a @@ -811,12 +824,12 @@ ConnectionData.prototype = Object.freeze({ // The best we can do is proceed without a transaction and hope // things won't break. if (wrappedConnections.has(this._identifier)) { - this._log.warn( + this._logger.warn( "A new transaction could not be started cause the wrapped connection had one in progress", ex ); } else { - this._log.warn( + this._logger.warn( "A transaction was already in progress, likely a nested transaction", ex ); @@ -831,7 +844,7 @@ ConnectionData.prototype = Object.freeze({ // It's possible that the exception has been caused by trying to // close the connection in the middle of a transaction. if (this._closeRequested) { - this._log.warn( + this._logger.warn( "Connection closed while performing a transaction", ex ); @@ -845,12 +858,12 @@ ConnectionData.prototype = Object.freeze({ caller_module, 1 ); - this._log.error( + this._logger.error( `The transaction requested by ${caller} timed out. Rolling back`, ex ); } else { - this._log.error( + this._logger.error( `Error during transaction requested by ${caller}. Rolling back`, ex ); @@ -860,9 +873,9 @@ ConnectionData.prototype = Object.freeze({ try { await this.execute("ROLLBACK TRANSACTION"); this._initiatedTransaction = false; - this._log.debug(`Roll back transaction`); + this._logger.debug(`Roll back transaction`); } catch (inner) { - this._log.error("Could not roll back transaction", inner); + this._logger.error("Could not roll back transaction", inner); } } } @@ -872,7 +885,7 @@ ConnectionData.prototype = Object.freeze({ // See comment above about connection being closed during transaction. if (this._closeRequested) { - this._log.warn( + this._logger.warn( "Connection closed before committing the transaction." ); throw new Error( @@ -884,9 +897,9 @@ ConnectionData.prototype = Object.freeze({ if (this._initiatedTransaction) { try { await this.execute("COMMIT TRANSACTION"); - this._log.debug(`Commit transaction`); + this._logger.debug(`Commit transaction`); } catch (ex) { - this._log.warn("Error committing transaction", ex); + this._logger.warn("Error committing transaction", ex); throw ex; } } @@ -902,7 +915,7 @@ ConnectionData.prototype = Object.freeze({ // Atomically update the queue before anyone else has a chance to enqueue // further transactions. this._transactionQueue = promise.catch(ex => { - this._log.error(ex); + this._logger.error(ex); }); // Make sure that we do not shutdown the connection during a transaction. @@ -914,7 +927,7 @@ ConnectionData.prototype = Object.freeze({ }, shrinkMemory() { - this._log.debug("Shrinking memory usage."); + this._logger.debug("Shrinking memory usage."); return this.execute("PRAGMA shrink_memory").finally(() => { this._clearIdleShrinkTimer(); }); @@ -927,12 +940,12 @@ ConnectionData.prototype = Object.freeze({ statement.finalize(); } this._cachedStatements.clear(); - this._log.debug("Discarded " + count + " cached statements."); + this._logger.debug("Discarded " + count + " cached statements."); return count; }, interrupt() { - this._log.debug("Trying to interrupt."); + this._logger.debug("Trying to interrupt."); this.ensureOpen(); this._dbConn.interrupt(); }, @@ -1018,15 +1031,15 @@ ConnectionData.prototype = Object.freeze({ // Don't incur overhead for serializing params unless the messages go // somewhere. - if (this._log.level <= lazy.Log.Level.Trace) { + if (this._logger.shouldLog("Trace")) { let msg = "Stmt #" + index + " " + sql; if (params) { msg += " - " + JSON.stringify(params); } - this._log.trace(msg); + this._logger.trace(msg); } else { - this._log.debug("Stmt #" + index + " starting"); + this._logger.debug("Stmt #" + index + " starting"); } let self = this; @@ -1052,20 +1065,20 @@ ConnectionData.prototype = Object.freeze({ pending.cancel(); }); } catch (e) { - self._log.warn("Exception when calling onRow callback", e); + self._logger.warn("Exception when calling onRow callback", e); } } }, handleError(error) { - self._log.warn( + self._logger.warn( "Error when executing SQL (" + error.result + "): " + error.message ); errors.push(error); }, handleCompletion(reason) { - self._log.debug("Stmt #" + index + " finished."); + self._logger.debug("Stmt #" + index + " finished."); self._pendingStatements.delete(index); switch (reason) { @@ -1256,11 +1269,7 @@ ConnectionData.prototype = Object.freeze({ * @return Promise<OpenedConnection> */ function openConnection(options) { - let log = lazy.Log.repository.getLoggerWithMessagePrefix( - "Sqlite.sys.mjs", - `ConnectionOpener: ` - ); - log.manageLevelFromPref("toolkit.sqlitejsm.loglevel"); + let logger = createLoggerWithPrefix("ConnectionOpener"); if (!options.path) { throw new Error("path not specified in connection options."); @@ -1350,7 +1359,7 @@ function openConnection(options) { let identifier = getIdentifierByFileName(PathUtils.filename(path)); - log.debug("Opening database: " + path + " (" + identifier + ")"); + logger.debug("Opening database: " + path + " (" + identifier + ")"); return new Promise((resolve, reject) => { let dbOpenOptions = Ci.mozIStorageService.OPEN_DEFAULT; @@ -1373,7 +1382,7 @@ function openConnection(options) { dbConnectionOptions, async (status, connection) => { if (!connection) { - log.error(`Could not open connection to ${path}: ${status}`); + logger.error(`Could not open connection to ${path}: ${status}`); let error = new Components.Exception( `Could not open connection to ${path}: ${status}`, status @@ -1381,7 +1390,7 @@ function openConnection(options) { reject(error); return; } - log.debug("Connection opened"); + logger.debug("Connection opened"); if (options.testDelayedOpenPromise) { await options.testDelayedOpenPromise; @@ -1407,7 +1416,7 @@ function openConnection(options) { ) ); } catch (ex) { - log.error("Could not open database", ex); + logger.error("Could not open database", ex); connection.asyncClose(); reject(ex); } @@ -1446,11 +1455,7 @@ function openConnection(options) { * @return Promise<OpenedConnection> */ function cloneStorageConnection(options) { - let log = lazy.Log.repository.getLoggerWithMessagePrefix( - "Sqlite.sys.mjs", - `ConnectionCloner: ` - ); - log.manageLevelFromPref("toolkit.sqlitejsm.loglevel"); + let logger = createLoggerWithPrefix("ConnectionCloner"); let source = options && options.connection; if (!source) { @@ -1484,16 +1489,16 @@ function cloneStorageConnection(options) { let path = source.databaseFile.path; let identifier = getIdentifierByFileName(PathUtils.filename(path)); - log.debug("Cloning database: " + path + " (" + identifier + ")"); + logger.debug("Cloning database: " + path + " (" + identifier + ")"); return new Promise((resolve, reject) => { source.asyncClone(!!options.readOnly, (status, connection) => { if (!connection) { - log.error("Could not clone connection: " + status); + logger.error("Could not clone connection: " + status); reject(new Error("Could not clone connection: " + status)); return; } - log.debug("Connection cloned"); + logger.debug("Connection cloned"); if (isClosed()) { connection.QueryInterface(Ci.mozIStorageAsyncConnection).asyncClose(); @@ -1510,7 +1515,7 @@ function cloneStorageConnection(options) { let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection); resolve(new OpenedConnection(conn, identifier, openedOptions)); } catch (ex) { - log.error("Could not clone database", ex); + logger.error("Could not clone database", ex); connection.asyncClose(); reject(ex); } @@ -1537,11 +1542,7 @@ function cloneStorageConnection(options) { * @return Promise<OpenedConnection> */ function wrapStorageConnection(options) { - let log = lazy.Log.repository.getLoggerWithMessagePrefix( - "Sqlite.sys.mjs", - `ConnectionCloner: ` - ); - log.manageLevelFromPref("toolkit.sqlitejsm.loglevel"); + let logger = createLoggerWithPrefix("ConnectionWrapper"); let connection = options && options.connection; if (!connection || !(connection instanceof Ci.mozIStorageAsyncConnection)) { @@ -1557,7 +1558,7 @@ function wrapStorageConnection(options) { let identifier = getIdentifierByFileName(connection.databaseFile.leafName); - log.debug("Wrapping database: " + identifier); + logger.debug("Wrapping database: " + identifier); return new Promise(resolve => { try { let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection); @@ -1567,7 +1568,7 @@ function wrapStorageConnection(options) { wrappedConnections.add(identifier); resolve(wrapper); } catch (ex) { - log.error("Could not wrap database", ex); + logger.error("Could not wrap database", ex); throw ex; } }); @@ -1687,13 +1688,24 @@ OpenedConnection.prototype = Object.freeze({ * Returns the maximum number of bound parameters for statements executed * on this connection. * - * @type {number} + * @returns {number} The bound parameters limit. */ get variableLimit() { return this.unsafeRawConnection.variableLimit; }, /** + * Set the the maximum number of bound parameters for statements executed + * on this connection. If the passed-in value is higher than the maximum + * default value, it will be silently truncated. + * + * @param {number} newLimit The bound parameters limit. + */ + set variableLimit(newLimit) { + this.unsafeRawConnection.variableLimit = newLimit; + }, + + /** * The integer schema version of the database. * * This is 0 if not schema version has been set. diff --git a/toolkit/modules/Troubleshoot.sys.mjs b/toolkit/modules/Troubleshoot.sys.mjs index 53259bcb67..43b8c1ca51 100644 --- a/toolkit/modules/Troubleshoot.sys.mjs +++ b/toolkit/modules/Troubleshoot.sys.mjs @@ -52,6 +52,7 @@ const PREFS_FOR_DISPLAY = [ "browser.startup.homepage", "browser.startup.page", "browser.tabs.", + "browser.toolbars.", "browser.urlbar.", "browser.zoom.", "doh-rollout.", @@ -412,6 +413,11 @@ var dataProviders = { remoteType = remoteType === "preallocated" ? "prealloc" : remoteType; } catch (e) {} + // We will split Utility by actor name, so do not do it now + if (remoteType === "utility") { + continue; + } + // The parent process is also managed by the ppmm (because // of non-remote tabs), but it doesn't have a remoteType. if (!remoteType) { @@ -425,6 +431,20 @@ var dataProviders = { } } + for (let i = 0; i < processInfo.children.length; i++) { + if (processInfo.children[i].type === "utility") { + for (let utilityWithActor of processInfo.children[i].utilityActors.map( + e => `utility_${e.actorName}` + )) { + if (remoteTypes[utilityWithActor]) { + remoteTypes[utilityWithActor]++; + } else { + remoteTypes[utilityWithActor] = 1; + } + } + } + } + try { let winUtils = Services.wm.getMostRecentWindow("").windowUtils; if (winUtils.gpuProcessPid != -1) { diff --git a/toolkit/modules/docs/AsyncShutdown.rst b/toolkit/modules/docs/AsyncShutdown.rst index 8d9e0d4388..4b0c5ef0f9 100644 --- a/toolkit/modules/docs/AsyncShutdown.rst +++ b/toolkit/modules/docs/AsyncShutdown.rst @@ -58,8 +58,8 @@ The following snippet presents an example of a client of FooService that has a s // Some client of FooService called FooClient - const { FooService } = ChromeUtils.import( - "resource://gre/modules/FooService.jsm" + const { FooService } = ChromeUtils.importESModule( + "resource://gre/modules/FooService.sys.mjs" ); // FooService.shutdown is the `client` capability of a `Barrier`. @@ -117,8 +117,8 @@ The following snippet presents FooClient2, a more sophisticated client of FooSer // Some client of FooService called FooClient2 - const { FooService } = ChromeUtils.import( - "resource://gre/modules/FooService.jsm" + const { FooService } = ChromeUtils.importESModule( + "resource://gre/modules/FooService.sys.mjs" ); FooService.shutdown.addBlocker( diff --git a/toolkit/modules/metrics.yaml b/toolkit/modules/metrics.yaml index a60fa077f5..9c7cedfe14 100644 --- a/toolkit/modules/metrics.yaml +++ b/toolkit/modules/metrics.yaml @@ -83,3 +83,23 @@ first_startup: expires: never send_in_pings: - first-startup + + new_profile: + type: boolean + description: > + True if FirstStartup was initted after a new profile was just created. If + false, this means that FirstStartup was initted with a pre-existing + profile, which is a no-op. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1877545 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1749345 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1877545 + data_sensitivity: + - technical + notification_emails: + - rhelmer@mozilla.com + - mconley@mozilla.com + expires: never + send_in_pings: + - first-startup diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build index 60e3ae20f9..07924cedba 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -133,6 +133,7 @@ with Files("WindowsRegistry.sys.mjs"): XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.toml"] BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"] MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.toml"] +MARIONETTE_MANIFESTS += ["tests/marionette/manifest.toml"] TESTING_JS_MODULES += [ "tests/modules/MockDocument.sys.mjs", @@ -163,7 +164,6 @@ EXTRA_JS_MODULES += [ "CreditCard.sys.mjs", "DateTimePickerPanel.sys.mjs", "DeferredTask.sys.mjs", - "Deprecated.sys.mjs", "E10SUtils.sys.mjs", "EventEmitter.sys.mjs", "FileUtils.sys.mjs", diff --git a/toolkit/modules/subprocess/.eslintrc.js b/toolkit/modules/subprocess/.eslintrc.js index 7640781589..ccec3c9d1b 100644 --- a/toolkit/modules/subprocess/.eslintrc.js +++ b/toolkit/modules/subprocess/.eslintrc.js @@ -6,8 +6,4 @@ module.exports = { extends: "../../components/extensions/.eslintrc.js", - - rules: { - "no-console": "off", - }, }; diff --git a/toolkit/modules/tests/browser/browser.toml b/toolkit/modules/tests/browser/browser.toml index 932b06d19e..b3c2b9cfda 100644 --- a/toolkit/modules/tests/browser/browser.toml +++ b/toolkit/modules/tests/browser/browser.toml @@ -13,8 +13,6 @@ support-files = [ ["browser_CreditCard.js"] skip-if = ["apple_silicon"] # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs -["browser_Deprecated.js"] - ["browser_Finder.js"] ["browser_FinderHighlighter.js"] diff --git a/toolkit/modules/tests/browser/browser_Deprecated.js b/toolkit/modules/tests/browser/browser_Deprecated.js deleted file mode 100644 index b718ba37e7..0000000000 --- a/toolkit/modules/tests/browser/browser_Deprecated.js +++ /dev/null @@ -1,140 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const PREF_DEPRECATION_WARNINGS = "devtools.errorconsole.deprecation_warnings"; - -// Using this named functions to test deprecation and the properly logged -// callstacks. -function basicDeprecatedFunction() { - Deprecated.warning("this method is deprecated.", "https://example.com"); - return true; -} - -function deprecationFunctionBogusCallstack() { - Deprecated.warning("this method is deprecated.", "https://example.com", { - caller: {}, - }); - return true; -} - -function deprecationFunctionCustomCallstack() { - // Get the nsIStackFrame that will contain the name of this function. - function getStack() { - return Components.stack; - } - Deprecated.warning( - "this method is deprecated.", - "https://example.com", - getStack() - ); - return true; -} - -var tests = [ - // Test deprecation warning without passing the callstack. - { - deprecatedFunction: basicDeprecatedFunction, - expectedObservation(aMessage) { - testAMessage(aMessage); - Assert.greater( - aMessage.indexOf("basicDeprecatedFunction"), - 0, - "Callstack is correctly logged." - ); - }, - }, - // Test a reported error when URL to documentation is not passed. - { - deprecatedFunction() { - Deprecated.warning("this method is deprecated."); - return true; - }, - expectedObservation(aMessage) { - Assert.greater( - aMessage.indexOf("must provide a URL"), - 0, - "Deprecation warning logged an empty URL argument." - ); - }, - }, - // Test deprecation with a bogus callstack passed as an argument (it will be - // replaced with the current call stack). - { - deprecatedFunction: deprecationFunctionBogusCallstack, - expectedObservation(aMessage) { - testAMessage(aMessage); - Assert.greater( - aMessage.indexOf("deprecationFunctionBogusCallstack"), - 0, - "Callstack is correctly logged." - ); - }, - }, - // Test deprecation with a valid custom callstack passed as an argument. - { - deprecatedFunction: deprecationFunctionCustomCallstack, - expectedObservation(aMessage) { - testAMessage(aMessage); - Assert.greater( - aMessage.indexOf("deprecationFunctionCustomCallstack"), - 0, - "Callstack is correctly logged." - ); - }, - // Set pref to true. - logWarnings: true, - }, -]; - -// Test Console Message attributes. -function testAMessage(aMessage) { - Assert.strictEqual( - aMessage.indexOf("DEPRECATION WARNING: this method is deprecated."), - 0, - "Deprecation is correctly logged." - ); - Assert.greater( - aMessage.indexOf("https://example.com"), - 0, - "URL is correctly logged." - ); -} - -add_task(async function test_setup() { - Services.prefs.setBoolPref(PREF_DEPRECATION_WARNINGS, true); - - // Check if Deprecated is loaded. - ok(Deprecated, "Deprecated object exists"); -}); - -add_task(async function test_pref_enabled() { - for (let [idx, test] of tests.entries()) { - info("Running test #" + idx); - - let promiseObserved = TestUtils.consoleMessageObserved(subject => { - let msg = subject.wrappedJSObject.arguments?.[0]; - return ( - msg.includes("DEPRECATION WARNING: ") || - msg.includes("must provide a URL") - ); - }); - - test.deprecatedFunction(); - - let msg = await promiseObserved; - - test.expectedObservation(msg.wrappedJSObject.arguments?.[0]); - } -}); - -add_task(async function test_pref_disabled() { - // Deprecation warnings will be logged only when the preference is set. - Services.prefs.setBoolPref(PREF_DEPRECATION_WARNINGS, false); - - let endFn = TestUtils.listenForConsoleMessages(); - basicDeprecatedFunction(); - - let messages = await endFn(); - Assert.equal(messages.length, 0, "Should not have received any messages"); -}); diff --git a/toolkit/modules/tests/marionette/manifest.toml b/toolkit/modules/tests/marionette/manifest.toml new file mode 100644 index 0000000000..08a103c289 --- /dev/null +++ b/toolkit/modules/tests/marionette/manifest.toml @@ -0,0 +1,4 @@ +[DEFAULT] +run-if = ["buildapp == 'browser'"] + +["test_first_startup.py"] diff --git a/toolkit/modules/tests/marionette/test_first_startup.py b/toolkit/modules/tests/marionette/test_first_startup.py new file mode 100644 index 0000000000..6d8bbce02d --- /dev/null +++ b/toolkit/modules/tests/marionette/test_first_startup.py @@ -0,0 +1,54 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_harness import MarionetteTestCase + + +class TestFirstStartup(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.quit() + self.marionette.instance.prefs = { + "browser.startup.homepage_override.mstone": "" + } + self.marionette.instance.app_args = ["-first-startup"] + + def test_new_profile(self): + """Test launching with --first-startup when a new profile was created. + + Launches the browser with --first-startup on a freshly created profile + and then ensures that FirstStartup.init ran successfully. + """ + + self.marionette.instance.switch_profile() + self.marionette.start_session() + self.marionette.set_context("chrome") + firstStartupInittedSuccessfully = self.marionette.execute_script( + """ + const { FirstStartup } = ChromeUtils.importESModule("resource://gre/modules/FirstStartup.sys.mjs"); + return FirstStartup.state == FirstStartup.SUCCESS + """ + ) + + self.assertTrue( + firstStartupInittedSuccessfully, "FirstStartup initted successfully" + ) + + def test_existing_profile(self): + """Test launching with --first-startup with a pre-existing profile. + + Launches the browser with --first-startup on a profile that has been + run before. Ensures that FirstStartup.init was never run. + """ + + self.marionette.start_session() + self.marionette.set_context("chrome") + firstStartupSkipped = self.marionette.execute_script( + """ + const { FirstStartup } = ChromeUtils.importESModule("resource://gre/modules/FirstStartup.sys.mjs"); + return FirstStartup.state == FirstStartup.NOT_STARTED + """ + ) + + self.assertTrue(firstStartupSkipped, "FirstStartup init skipped") diff --git a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js index 8f725fc78d..a0d12b8a6a 100644 --- a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js +++ b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js @@ -1257,7 +1257,7 @@ add_task(async function test_GMPExtractor_paths() { if (AppConstants.platform == "macosx") { await Assert.rejects( IOUtils.getMacXAttr(extractedFile, "com.apple.quarantine"), - /NotFoundError: The file `.+' does not have an extended attribute `com.apple.quarantine'/, + /NotFoundError: Could not get extended attribute `com.apple.quarantine' from `.+': the file does not have the attribute/, "The 'com.apple.quarantine' attribute should not be present" ); } diff --git a/toolkit/modules/tests/xpcshell/test_Services.js b/toolkit/modules/tests/xpcshell/test_Services.js index 55c762fdad..8169a23bbd 100644 --- a/toolkit/modules/tests/xpcshell/test_Services.js +++ b/toolkit/modules/tests/xpcshell/test_Services.js @@ -28,7 +28,6 @@ function run_test() { checkService("cookies", Ci.nsICookieManager); checkService("dirsvc", Ci.nsIDirectoryService); checkService("dirsvc", Ci.nsIProperties); - checkService("DOMRequest", Ci.nsIDOMRequestService); checkService("domStorageManager", Ci.nsIDOMStorageManager); checkService("droppedLinkHandler", Ci.nsIDroppedLinkHandler); checkService("eTLD", Ci.nsIEffectiveTLDService); diff --git a/toolkit/modules/tests/xpcshell/test_firstStartup.js b/toolkit/modules/tests/xpcshell/test_firstStartup.js index 02f126d66f..445d3a0c72 100644 --- a/toolkit/modules/tests/xpcshell/test_firstStartup.js +++ b/toolkit/modules/tests/xpcshell/test_firstStartup.js @@ -24,11 +24,13 @@ add_task(async function test_success() { updateAppInfo(); let submissionPromise; + FirstStartup.resetForTesting(); if (AppConstants.MOZ_NORMANDY || AppConstants.MOZ_UPDATE_AGENT) { submissionPromise = new Promise(resolve => { GleanPings.firstStartup.testBeforeNextSubmit(() => { Assert.equal(FirstStartup.state, FirstStartup.SUCCESS); + Assert.ok(Glean.firstStartup.newProfile.testGetValue()); Assert.equal( Glean.firstStartup.statusCode.testGetValue(), FirstStartup.SUCCESS @@ -49,6 +51,7 @@ add_task(async function test_success() { submissionPromise = new Promise(resolve => { GleanPings.firstStartup.testBeforeNextSubmit(() => { Assert.equal(FirstStartup.state, FirstStartup.UNSUPPORTED); + Assert.ok(Glean.firstStartup.newProfile.testGetValue()); Assert.equal( Glean.firstStartup.statusCode.testGetValue(), FirstStartup.UNSUPPORTED @@ -58,13 +61,14 @@ add_task(async function test_success() { }); } - FirstStartup.init(); + FirstStartup.init(true /* newProfile */); await submissionPromise; }); add_task(async function test_timeout() { updateAppInfo(); Services.prefs.setIntPref(PREF_TIMEOUT, 0); + FirstStartup.resetForTesting(); let submissionPromise; @@ -73,6 +77,7 @@ add_task(async function test_timeout() { GleanPings.firstStartup.testBeforeNextSubmit(() => { Assert.equal(FirstStartup.state, FirstStartup.TIMED_OUT); Assert.ok(Glean.firstStartup.elapsed.testGetValue() > 0); + Assert.ok(Glean.firstStartup.newProfile.testGetValue()); if (AppConstants.MOZ_NORMANDY) { Assert.ok(Glean.firstStartup.normandyInitTime.testGetValue() > 0); @@ -90,11 +95,27 @@ add_task(async function test_timeout() { GleanPings.firstStartup.testBeforeNextSubmit(() => { Assert.equal(FirstStartup.state, FirstStartup.UNSUPPORTED); Assert.equal(Glean.firstStartup.elapsed.testGetValue(), 0); + Assert.ok(Glean.firstStartup.newProfile.testGetValue()); resolve(); }); }); } - FirstStartup.init(); + FirstStartup.init(true /* newProfile */); + await submissionPromise; +}); + +add_task(async function test_existing_profile() { + FirstStartup.resetForTesting(); + + let submissionPromise = new Promise(resolve => { + GleanPings.firstStartup.testBeforeNextSubmit(() => { + Assert.equal(FirstStartup.state, FirstStartup.NOT_STARTED); + Assert.ok(!Glean.firstStartup.newProfile.testGetValue()); + resolve(); + }); + }); + + FirstStartup.init(false /* newProfile */); await submissionPromise; }); diff --git a/toolkit/modules/tests/xpcshell/test_sqlite.js b/toolkit/modules/tests/xpcshell/test_sqlite.js index 2c3ede46d6..e0a79e137d 100644 --- a/toolkit/modules/tests/xpcshell/test_sqlite.js +++ b/toolkit/modules/tests/xpcshell/test_sqlite.js @@ -118,9 +118,8 @@ add_task(async function test_open_normal_error() { // Ensure that our database doesn't already exist. let path = PathUtils.join(PROFILE_DIR, "corrupt.sqlite"); - await Assert.rejects( - IOUtils.stat(path), - /Could not stat file\(.*\) because it does not exist/, + Assert.ok( + !(await IOUtils.exists(path)), "Database file should not exist yet" ); diff --git a/toolkit/modules/third_party/fathom/README b/toolkit/modules/third_party/fathom/README index 5f7ba3b4cb..7c2f0980fa 100644 --- a/toolkit/modules/third_party/fathom/README +++ b/toolkit/modules/third_party/fathom/README @@ -14,4 +14,4 @@ In order to regenerate this file, do the following: $ make bundleESModule $ export MOZ_FATHOM="../../mozilla-central/toolkit/modules/third_party/fathom" - $ cat $MOZ_FATHOM/fx-header dist/fathom.js > $MOZ_FATHOM/fathom.jsm + $ cat $MOZ_FATHOM/fx-header dist/fathom.js > $MOZ_FATHOM/fathom.mjs diff --git a/toolkit/modules/third_party/fathom/fathom.mjs b/toolkit/modules/third_party/fathom/fathom.mjs index c1d984a9e3..be60013261 100644 --- a/toolkit/modules/third_party/fathom/fathom.mjs +++ b/toolkit/modules/third_party/fathom/fathom.mjs @@ -1,5 +1,5 @@ /* -DO NOT TOUCH fathom.jsm DIRECTLY. See the README for instructions. +DO NOT TOUCH fathom.mjs DIRECTLY. See the README for instructions. */ /* This Source Code Form is subject to the terms of the Mozilla Public |