915 lines
28 KiB
JavaScript
915 lines
28 KiB
JavaScript
/* 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 lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
|
|
AppProvidedSearchEngine:
|
|
"moz-src:///toolkit/components/search/AppProvidedSearchEngine.sys.mjs",
|
|
DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
|
|
ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
|
|
SearchUtils: "moz-src:///toolkit/components/search/SearchUtils.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
|
|
return console.createInstance({
|
|
prefix: "SearchSettings",
|
|
maxLogLevel: lazy.SearchUtils.loggingEnabled ? "Debug" : "Warn",
|
|
});
|
|
});
|
|
|
|
const SETTINGS_FILENAME = "search.json.mozlz4";
|
|
|
|
/**
|
|
* A map of engine ids to their previous names. These are required for
|
|
* ensuring that user's settings are correctly migrated for users upgrading
|
|
* from a settings file prior to settings version 7 (Firefox 108).
|
|
*
|
|
* @type {Map<string, string>}
|
|
*/
|
|
const ENGINE_ID_TO_OLD_NAME_MAP = new Map([
|
|
["wikipedia-hy", "Wikipedia (hy)"],
|
|
["wikipedia-kn", "Wikipedia (kn)"],
|
|
["wikipedia-lv", "Vikipēdija"],
|
|
["wikipedia-NO", "Wikipedia (no)"],
|
|
["wikipedia-el", "Wikipedia (el)"],
|
|
["wikipedia-lt", "Wikipedia (lt)"],
|
|
["wikipedia-my", "Wikipedia (my)"],
|
|
["wikipedia-pa", "Wikipedia (pa)"],
|
|
["wikipedia-pt", "Wikipedia (pt)"],
|
|
["wikipedia-si", "Wikipedia (si)"],
|
|
["wikipedia-tr", "Wikipedia (tr)"],
|
|
]);
|
|
|
|
/**
|
|
* This class manages the saves search settings.
|
|
*
|
|
* Global settings can be saved and obtained from this class via the
|
|
* `*Attribute` methods.
|
|
*/
|
|
export class SearchSettings {
|
|
constructor(searchService) {
|
|
this.#searchService = searchService;
|
|
|
|
// Once the search service has initialized, schedule a write to ensure
|
|
// that any settings that may have changed or need updating are handled.
|
|
searchService.promiseInitialized.then(() => {
|
|
this._delayedWrite();
|
|
});
|
|
}
|
|
|
|
QueryInterface = ChromeUtils.generateQI([Ci.nsIObserver]);
|
|
|
|
// Delay for batching invalidation of the JSON settings (ms)
|
|
static SETTINGS_INVALIDATION_DELAY = 1000;
|
|
|
|
get #settingsFilePath() {
|
|
return PathUtils.join(PathUtils.profileDir, SETTINGS_FILENAME);
|
|
}
|
|
|
|
/**
|
|
* A reference to the pending DeferredTask, if there is one.
|
|
*/
|
|
_batchTask = null;
|
|
|
|
/**
|
|
* A reference to the search service so that we can save the engines list.
|
|
*/
|
|
#searchService = null;
|
|
|
|
/*
|
|
* The user's settings file read from disk so we can persist metadata for
|
|
* engines that are default or hidden, the user's locale and region, hashes
|
|
* for the loadPath, and hashes for default and private default engines.
|
|
* This is the JSON we read from disk and save to disk when there's an update
|
|
* to the settings.
|
|
*
|
|
* Structure of settings:
|
|
* Object { version: <number>,
|
|
* engines: [...],
|
|
* metaData: {...},
|
|
* }
|
|
*
|
|
* Settings metaData is the active metadata for setting and getting attributes.
|
|
* When a new metadata attribute is set, we save it to #settings.metaData and
|
|
* write #settings to disk.
|
|
*
|
|
* #settings.metaData attributes:
|
|
* @property {string} current
|
|
* The current user-set default engine. The associated hash is called
|
|
* 'hash'.
|
|
* @property {string} private
|
|
* The current user-set private engine. The associated hash is called
|
|
* 'privateHash'.
|
|
* The current and prviate objects have associated hash fields to validate
|
|
* the value is set by the application.
|
|
* @property {string} appDefaultEngine
|
|
* @property {string} channel
|
|
* Configuration is restricted to the specified channel. ESR is an example
|
|
* of a channel.
|
|
* @property {string} distroID
|
|
* Specifies which distribution the default engine is included in.
|
|
* @property {string} experiment
|
|
* Specifies if the application is running on an experiment.
|
|
* @property {string} locale
|
|
* @property {string} region
|
|
* @property {boolean} useSavedOrder
|
|
* True if the user's order information stored in settings is used.
|
|
*
|
|
*/
|
|
#settings = null;
|
|
|
|
/**
|
|
* #cachedSettings is updated when we read the settings from disk and when
|
|
* we write settings to disk. #cachedSettings is compared with #settings
|
|
* before we do a write to disk. If there's no change to the settings
|
|
* attributes, then we don't write the settings to disk.
|
|
*
|
|
* This is a deep copy of #settings.
|
|
*
|
|
* @type {object}
|
|
*/
|
|
#cachedSettings = {};
|
|
|
|
addObservers() {
|
|
Services.obs.addObserver(this, lazy.SearchUtils.TOPIC_ENGINE_MODIFIED);
|
|
Services.obs.addObserver(this, lazy.SearchUtils.TOPIC_SEARCH_SERVICE);
|
|
}
|
|
|
|
/**
|
|
* Cleans up, removing observers.
|
|
*/
|
|
removeObservers() {
|
|
Services.obs.removeObserver(this, lazy.SearchUtils.TOPIC_ENGINE_MODIFIED);
|
|
Services.obs.removeObserver(this, lazy.SearchUtils.TOPIC_SEARCH_SERVICE);
|
|
}
|
|
|
|
/**
|
|
* Whether the last `get` reset the settings because they were corrupt.
|
|
*/
|
|
lastGetCorrupt = false;
|
|
|
|
/**
|
|
* Reads the settings file.
|
|
*
|
|
* @param {string} origin
|
|
* If this parameter is "test", then the settings will not be written. As
|
|
* some tests manipulate the settings directly, we allow turning off writing to
|
|
* avoid writing stale settings data.
|
|
* @returns {Promise<object>}
|
|
* Returns the settings file data.
|
|
*/
|
|
async get(origin = "") {
|
|
this.lastGetCorrupt = false;
|
|
|
|
let json;
|
|
await this._ensurePendingWritesCompleted(origin);
|
|
try {
|
|
json = await IOUtils.readJSON(this.#settingsFilePath, {
|
|
decompress: true,
|
|
});
|
|
if (!json.engines || !json.engines.length) {
|
|
throw new Error("no engine in the file");
|
|
}
|
|
} catch (ex) {
|
|
if (DOMException.isInstance(ex) && ex.name === "NotFoundError") {
|
|
lazy.logConsole.debug("get: No settings file exists, new profile?", ex);
|
|
return this.#resetSettings(false);
|
|
}
|
|
lazy.logConsole.error("get: Settings file empty or corrupt.", ex);
|
|
return this.#resetSettings(true);
|
|
}
|
|
|
|
this.#settings = json;
|
|
this.#cachedSettings = structuredClone(json);
|
|
|
|
if (!this.#settings.metaData) {
|
|
this.#settings.metaData = {};
|
|
}
|
|
|
|
try {
|
|
await this.#migrateSettings();
|
|
} catch (ex) {
|
|
lazy.logConsole.error("get: Migration failed.", ex);
|
|
return this.#resetSettings(true);
|
|
}
|
|
|
|
return structuredClone(json);
|
|
}
|
|
|
|
/**
|
|
* Resets the search settings without writing to disk yet.
|
|
*
|
|
* If the reset is due to a corrupt settings file, the corrupt file is
|
|
* backed up, the lastSettingsCorruptTime pref is set to the current time,
|
|
* and this.lastGetCorrupt is set to true.
|
|
*
|
|
* @param {boolean} corrupt
|
|
* Whether the reset is carried out because the settings are corrupt.
|
|
* @returns {Promise<object>}
|
|
* New empty search settings.
|
|
*/
|
|
async #resetSettings(corrupt) {
|
|
this.#settings = { metaData: {} };
|
|
this.#cachedSettings = {};
|
|
|
|
if (corrupt) {
|
|
this.lastGetCorrupt = true;
|
|
Services.prefs.setIntPref(
|
|
lazy.SearchUtils.BROWSER_SEARCH_PREF + "lastSettingsCorruptTime",
|
|
Date.now() / 1000
|
|
);
|
|
try {
|
|
await IOUtils.move(
|
|
this.#settingsFilePath,
|
|
this.#settingsFilePath + ".bak"
|
|
);
|
|
} catch (ex) {
|
|
lazy.logConsole.warn(
|
|
"#resetSettings: Unable to create backup of corrupt settings file.",
|
|
ex
|
|
);
|
|
}
|
|
}
|
|
|
|
return structuredClone(this.#settings);
|
|
}
|
|
|
|
/**
|
|
* Queues writing the settings until after SETTINGS_INVALIDATION_DELAY. If there
|
|
* is a currently queued task then it will be restarted.
|
|
*/
|
|
_delayedWrite() {
|
|
if (this._batchTask) {
|
|
this._batchTask.disarm();
|
|
} else {
|
|
let task = async () => {
|
|
if (
|
|
!this.#searchService.isInitialized ||
|
|
this.#searchService._reloadingEngines
|
|
) {
|
|
// Re-arm the task as we don't want to save potentially incomplete
|
|
// information during the middle of (re-)initializing.
|
|
this._batchTask.arm();
|
|
return;
|
|
}
|
|
lazy.logConsole.debug("batchTask: Invalidating engine settings");
|
|
await this._write();
|
|
};
|
|
this._batchTask = new lazy.DeferredTask(
|
|
task,
|
|
SearchSettings.SETTINGS_INVALIDATION_DELAY
|
|
);
|
|
}
|
|
this._batchTask.arm();
|
|
}
|
|
|
|
/**
|
|
* Ensures any pending writes of the settings are completed.
|
|
*
|
|
* @param {string} origin
|
|
* If this parameter is "test", then the settings will not be written. As
|
|
* some tests manipulate the settings directly, we allow turning off writing to
|
|
* avoid writing stale settings data.
|
|
*/
|
|
async _ensurePendingWritesCompleted(origin = "") {
|
|
// Before we read the settings file, first make sure all pending tasks are clear.
|
|
if (!this._batchTask) {
|
|
return;
|
|
}
|
|
lazy.logConsole.debug("finalizing batch task");
|
|
let task = this._batchTask;
|
|
this._batchTask = null;
|
|
// Tests manipulate the settings directly, so let's not double-write with
|
|
// stale settings data here.
|
|
if (origin == "test") {
|
|
task.disarm();
|
|
} else {
|
|
await task.finalize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes the settings to disk (no delay).
|
|
*/
|
|
async _write() {
|
|
if (this._batchTask) {
|
|
this._batchTask.disarm();
|
|
}
|
|
|
|
let settings = {};
|
|
|
|
// Allows us to force a settings refresh should the settings format change.
|
|
settings.version = lazy.SearchUtils.SETTINGS_VERSION;
|
|
settings.engines = [...this.#searchService._engines.values()].map(engine =>
|
|
JSON.parse(JSON.stringify(engine))
|
|
);
|
|
settings.metaData = this.#settings.metaData;
|
|
|
|
// Persist metadata for AppProvided engines even if they aren't currently
|
|
// active, this means if they become active again their settings
|
|
// will be restored.
|
|
if (this.#settings?.engines) {
|
|
for (let engine of this.#settings.engines) {
|
|
let included = settings.engines.some(e => e._name == engine._name);
|
|
if (engine._isAppProvided && !included) {
|
|
settings.engines.push(engine);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the local copy.
|
|
this.#settings = settings;
|
|
|
|
try {
|
|
if (!settings.engines.length) {
|
|
throw new Error("cannot write without any engine.");
|
|
}
|
|
|
|
if (this.isCurrentAndCachedSettingsEqual()) {
|
|
lazy.logConsole.debug(
|
|
"_write: Settings unchanged. Did not write to disk."
|
|
);
|
|
Services.obs.notifyObservers(
|
|
null,
|
|
lazy.SearchUtils.TOPIC_SEARCH_SERVICE,
|
|
"write-prevented-when-settings-unchanged"
|
|
);
|
|
Services.obs.notifyObservers(
|
|
null,
|
|
lazy.SearchUtils.TOPIC_SEARCH_SERVICE,
|
|
"write-settings-to-disk-complete"
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// At this point, the settings and cached settings are different. We
|
|
// write settings to disk and update #cachedSettings.
|
|
this.#cachedSettings = structuredClone(this.#settings);
|
|
|
|
lazy.logConsole.debug("_write: Writing to settings file.");
|
|
await IOUtils.writeJSON(this.#settingsFilePath, settings, {
|
|
compress: true,
|
|
tmpPath: this.#settingsFilePath + ".tmp",
|
|
});
|
|
lazy.logConsole.debug("_write: settings file written to disk.");
|
|
Services.obs.notifyObservers(
|
|
null,
|
|
lazy.SearchUtils.TOPIC_SEARCH_SERVICE,
|
|
"write-settings-to-disk-complete"
|
|
);
|
|
} catch (ex) {
|
|
lazy.logConsole.error("_write: Could not write to settings file:", ex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets an attribute without verification.
|
|
*
|
|
* @param {string} name
|
|
* The name of the attribute to set.
|
|
* @param {*} val
|
|
* The value to set.
|
|
*/
|
|
setMetaDataAttribute(name, val) {
|
|
this.#settings.metaData[name] = val;
|
|
this._delayedWrite();
|
|
}
|
|
|
|
/**
|
|
* Sets a verified attribute. This will save an additional hash
|
|
* value, that can be verified when reading back.
|
|
*
|
|
* @param {string} name
|
|
* The name of the attribute to set.
|
|
* @param {*} val
|
|
* The value to set.
|
|
*/
|
|
setVerifiedMetaDataAttribute(name, val) {
|
|
this.#settings.metaData[name] = val;
|
|
this.#settings.metaData[this.getHashName(name)] =
|
|
lazy.SearchUtils.getVerificationHash(val);
|
|
this._delayedWrite();
|
|
}
|
|
|
|
/**
|
|
* Gets an attribute without verification.
|
|
*
|
|
* @param {string} name
|
|
* The name of the attribute to get.
|
|
* @returns {*}
|
|
* The value of the attribute, or undefined if not known.
|
|
*/
|
|
getMetaDataAttribute(name) {
|
|
return this.#settings.metaData[name] ?? undefined;
|
|
}
|
|
|
|
/**
|
|
* Gets a copy of the settings metadata.
|
|
*
|
|
* @returns {*}
|
|
* A copy of the settings metadata object.
|
|
*/
|
|
getSettingsMetaData() {
|
|
return { ...this.#settings.metaData };
|
|
}
|
|
|
|
/**
|
|
* Gets a verified attribute.
|
|
*
|
|
* @param {string} name
|
|
* The name of the attribute to get.
|
|
* @param {boolean} isAppProvided
|
|
* |true| if the engine associated with the attribute is an application
|
|
* provided engine.
|
|
* @returns {*}
|
|
* The value of the attribute.
|
|
* We return undefined if the value of the attribute is not known or does
|
|
* not match the verification hash.
|
|
*/
|
|
getVerifiedMetaDataAttribute(name, isAppProvided) {
|
|
let attribute = this.getMetaDataAttribute(name);
|
|
|
|
// If the selected engine is an application provided one, we can relax the
|
|
// verification hash check to reduce the annoyance for users who
|
|
// backup/sync their profile in custom ways.
|
|
if (isAppProvided) {
|
|
return attribute;
|
|
}
|
|
|
|
if (
|
|
attribute &&
|
|
this.getMetaDataAttribute(this.getHashName(name)) !=
|
|
lazy.SearchUtils.getVerificationHash(attribute)
|
|
) {
|
|
lazy.logConsole.warn(
|
|
"getVerifiedMetaDataAttribute, invalid hash for",
|
|
name
|
|
);
|
|
return undefined;
|
|
}
|
|
return attribute;
|
|
}
|
|
|
|
/**
|
|
* Sets an attribute in #settings.engines._metaData
|
|
*
|
|
* @param {string} engineName
|
|
* The name of the engine.
|
|
* @param {string} property
|
|
* The name of the attribute to set.
|
|
* @param {*} value
|
|
* The value to set.
|
|
*/
|
|
setEngineMetaDataAttribute(engineName, property, value) {
|
|
let engines = [...this.#searchService._engines.values()];
|
|
let engine = engines.find(e => e._name == engineName);
|
|
if (engine) {
|
|
engine._metaData[property] = value;
|
|
this._delayedWrite();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets an attribute from #settings.engines._metaData
|
|
*
|
|
* @param {string} engineName
|
|
* The name of the engine.
|
|
* @param {string} property
|
|
* The name of the attribute to get.
|
|
* @returns {*}
|
|
* The value of the attribute, or undefined if not known.
|
|
*/
|
|
getEngineMetaDataAttribute(engineName, property) {
|
|
let engine = this.#settings.engines.find(e => e._name == engineName);
|
|
return engine._metaData[property] ?? undefined;
|
|
}
|
|
|
|
/**
|
|
* Returns the name for the hash for a particular attribute. This is
|
|
* necessary because the default engine ID property is named `current`
|
|
* with its hash as `hash`. All other hashes are in the `<name>Hash` format.
|
|
*
|
|
* @param {string} name
|
|
* The name of the attribute to get the hash name for.
|
|
* @returns {string}
|
|
* The hash name to use.
|
|
*/
|
|
getHashName(name) {
|
|
// The "current" check remains here because we need to retrieve the
|
|
// "current" hash name for the migration of engine ids. After the migration,
|
|
// the "current" property is no longer used because we now store
|
|
// "defaultEngineId" instead.
|
|
if (name == "current") {
|
|
return "hash";
|
|
}
|
|
return name + "Hash";
|
|
}
|
|
|
|
/**
|
|
* Handles shutdown; writing the settings if necessary.
|
|
*
|
|
* @param {object} state
|
|
* The shutdownState object that is used to help analyzing the shutdown
|
|
* state in case of a crash or shutdown timeout.
|
|
*/
|
|
async shutdown(state) {
|
|
if (!this._batchTask) {
|
|
return;
|
|
}
|
|
state.step = "Finalizing batched task";
|
|
try {
|
|
await this._batchTask.finalize();
|
|
state.step = "Batched task finalized";
|
|
} catch (ex) {
|
|
state.step = "Batched task failed to finalize";
|
|
|
|
state.latestError.message = "" + ex;
|
|
if (ex && typeof ex == "object") {
|
|
state.latestError.stack = ex.stack || undefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
// nsIObserver
|
|
observe(engine, topic, verb) {
|
|
switch (topic) {
|
|
case lazy.SearchUtils.TOPIC_ENGINE_MODIFIED:
|
|
switch (verb) {
|
|
case lazy.SearchUtils.MODIFIED_TYPE.ADDED:
|
|
case lazy.SearchUtils.MODIFIED_TYPE.CHANGED:
|
|
case lazy.SearchUtils.MODIFIED_TYPE.REMOVED:
|
|
this._delayedWrite();
|
|
break;
|
|
case lazy.SearchUtils.MODIFIED_TYPE.ICON_CHANGED:
|
|
// Application Provided Search Engines have their icons stored in
|
|
// Remote Settings, so we don't need to update the saved settings.
|
|
if (
|
|
!(engine?.wrappedJSObject instanceof lazy.AppProvidedSearchEngine)
|
|
) {
|
|
this._delayedWrite();
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case lazy.SearchUtils.TOPIC_SEARCH_SERVICE:
|
|
switch (verb) {
|
|
case "engines-reloaded":
|
|
this._delayedWrite();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compares the #settings and #cachedSettings objects.
|
|
*
|
|
* @returns {boolean}
|
|
* True if the objects have the same property and values.
|
|
*/
|
|
isCurrentAndCachedSettingsEqual() {
|
|
return lazy.ObjectUtils.deepEqual(this.#settings, this.#cachedSettings);
|
|
}
|
|
|
|
/**
|
|
* This function writes to settings versions 6 and below. It does two
|
|
* updates:
|
|
* 1) Store engine ids.
|
|
* 2) Store "defaultEngineId" and "privateDefaultEngineId" to replace
|
|
* "current" and "private" because we are no longer referencing the
|
|
* "current" and "private" attributes with engine names as their values.
|
|
*
|
|
* @param {object} clonedSettings
|
|
* The SearchService holds a deep copy of the settings file object. This
|
|
* clonedSettings is passed in as an argument from SearchService.
|
|
*/
|
|
migrateEngineIds(clonedSettings) {
|
|
if (clonedSettings.version <= 6) {
|
|
lazy.logConsole.debug("migrateEngineIds: start");
|
|
|
|
for (let engineSettings of clonedSettings.engines) {
|
|
let engine = this.#getEngineByName(engineSettings._name);
|
|
|
|
if (engine) {
|
|
// Store the engine id
|
|
engineSettings.id = engine.id;
|
|
}
|
|
}
|
|
|
|
let currentDefaultEngine = this.#getEngineByName(
|
|
clonedSettings.metaData.current
|
|
);
|
|
let privateDefaultEngine = this.#getEngineByName(
|
|
clonedSettings.metaData.private
|
|
);
|
|
|
|
// As per SearchService._getEngineDefault, we relax the verification hash
|
|
// check for application provided engines to reduce the annoyance for
|
|
// users who backup/sync their profile in custom ways.
|
|
if (
|
|
currentDefaultEngine &&
|
|
(currentDefaultEngine.isAppProvided ||
|
|
lazy.SearchUtils.getVerificationHash(
|
|
clonedSettings.metaData.current
|
|
) == clonedSettings.metaData[this.getHashName("current")])
|
|
) {
|
|
// Store the defaultEngineId
|
|
this.setVerifiedMetaDataAttribute(
|
|
"defaultEngineId",
|
|
currentDefaultEngine.id
|
|
);
|
|
} else {
|
|
this.setVerifiedMetaDataAttribute("defaultEngineId", "");
|
|
}
|
|
|
|
if (
|
|
privateDefaultEngine &&
|
|
(privateDefaultEngine.isAppProvided ||
|
|
lazy.SearchUtils.getVerificationHash(
|
|
clonedSettings.metaData.private
|
|
) == clonedSettings.metaData[this.getHashName("private")])
|
|
) {
|
|
// Store the privateDefaultEngineId
|
|
this.setVerifiedMetaDataAttribute(
|
|
"privateDefaultEngineId",
|
|
privateDefaultEngine.id
|
|
);
|
|
} else {
|
|
this.setVerifiedMetaDataAttribute("privateDefaultEngineId", "");
|
|
}
|
|
|
|
lazy.logConsole.debug("migrateEngineIds: done");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the settings for the engine, based on the version of the settings
|
|
* passed in. Older versions of settings used the engine name as the key,
|
|
* whereas newer versions now use the engine id.
|
|
*
|
|
* @param {object} settings
|
|
* The saved settings object.
|
|
* @param {string} engineId
|
|
* The id of the engine.
|
|
* @param {string} engineName
|
|
* The name of the engine.
|
|
* @returns {object|undefined}
|
|
* The engine settings if found, undefined otherwise.
|
|
*/
|
|
static findSettingsForEngine(settings, engineId, engineName) {
|
|
if (settings.version <= 6) {
|
|
let engineSettings = settings.engines?.find(e => e._name == engineName);
|
|
if (!engineSettings) {
|
|
// If we can't find the engine settings with the current name,
|
|
// see if there was an older name.
|
|
let oldEngineName = ENGINE_ID_TO_OLD_NAME_MAP.get(engineId);
|
|
if (oldEngineName) {
|
|
engineSettings = settings.engines?.find(
|
|
e => e._name == oldEngineName
|
|
);
|
|
}
|
|
}
|
|
return engineSettings;
|
|
}
|
|
return settings.engines?.find(e => e.id == engineId);
|
|
}
|
|
|
|
/**
|
|
* Returns the engine associated with the name without SearchService
|
|
* initialization checks.
|
|
*
|
|
* @param {string} engineName
|
|
* The name of the engine.
|
|
* @returns {nsISearchEngine}
|
|
* The associated engine if found, null otherwise.
|
|
*/
|
|
#getEngineByName(engineName) {
|
|
for (let engine of this.#searchService._engines.values()) {
|
|
if (engine.name == engineName) {
|
|
return engine;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Migrates older settings to the latest version.
|
|
* Does not migrate the engine IDs yet because that happens
|
|
* after the ApplicationProvidedEngines have been loaded.
|
|
*/
|
|
async #migrateSettings() {
|
|
this.#migrateTo6();
|
|
this.#migrateTo8();
|
|
this.#migrateTo9();
|
|
this.#migrateTo10();
|
|
this.#migrateTo11();
|
|
await this.#migrateTo12();
|
|
}
|
|
|
|
#migrateTo6() {
|
|
// Versions of gecko older than 82 stored the order flag as a preference.
|
|
// See bug 1642995.
|
|
if (
|
|
this.#settings.version < 6 ||
|
|
!("useSavedOrder" in this.#settings.metaData)
|
|
) {
|
|
const prefName = lazy.SearchUtils.BROWSER_SEARCH_PREF + "useDBForOrder";
|
|
let useSavedOrder = Services.prefs.getBoolPref(prefName, false);
|
|
|
|
this.setMetaDataAttribute("useSavedOrder", useSavedOrder);
|
|
|
|
// Clear the old pref so it isn't lying around.
|
|
Services.prefs.clearUserPref(prefName);
|
|
}
|
|
}
|
|
|
|
#migrateTo8() {
|
|
// The load path is changed to better differentiate policy/user/
|
|
// add-on engines for telemetry. See bug 1801813.
|
|
if (this.#settings.version < 8 && Array.isArray(this.#settings.engines)) {
|
|
for (let engine of this.#settings.engines) {
|
|
if (!engine._loadPath) {
|
|
continue;
|
|
}
|
|
if (engine._loadPath.includes("set-via-policy")) {
|
|
engine._loadPath = "[policy]";
|
|
} else if (engine._loadPath.includes("set-via-user")) {
|
|
engine._loadPath = "[user]";
|
|
} else if (
|
|
engine._loadPath.startsWith("[other]addEngineWithDetails:")
|
|
) {
|
|
engine._loadPath = engine._loadPath.replace(
|
|
"[other]addEngineWithDetails:",
|
|
"[addon]"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#migrateTo9() {
|
|
// The hiddenOneOffs pref is moved to the search settings.
|
|
// See bug 1643887.
|
|
if (this.#settings.version < 9 && this.#settings.engines) {
|
|
const hiddenOneOffsPrefs = Services.prefs.getStringPref(
|
|
"browser.search.hiddenOneOffs",
|
|
""
|
|
);
|
|
for (const engine of this.#settings.engines) {
|
|
engine._metaData.hideOneOffButton = hiddenOneOffsPrefs.includes(
|
|
engine._name
|
|
);
|
|
}
|
|
Services.prefs.clearUserPref("browser.search.hiddenOneOffs");
|
|
}
|
|
}
|
|
|
|
#migrateTo10() {
|
|
// The format of the IDs of app provided engines is changed.
|
|
// See bug 1870687.
|
|
if (
|
|
this.#settings.version > 6 &&
|
|
this.#settings.version < 10 &&
|
|
this.#settings.engines
|
|
) {
|
|
let changedEngines = new Map();
|
|
for (let engine of this.#settings.engines) {
|
|
if (engine._isAppProvided && engine.id) {
|
|
let oldId = engine.id;
|
|
engine.id = engine.id
|
|
.replace("@search.mozilla.orgdefault", "")
|
|
.replace("@search.mozilla.org", "-");
|
|
changedEngines.set(oldId, engine.id);
|
|
}
|
|
}
|
|
|
|
const PROPERTIES_CONTAINING_IDS = [
|
|
"privateDefaultEngineId",
|
|
"appDefaultEngineId",
|
|
"defaultEngineId",
|
|
];
|
|
|
|
for (let prop of PROPERTIES_CONTAINING_IDS) {
|
|
if (changedEngines.has(this.#settings.metaData[prop])) {
|
|
this.#settings.metaData[prop] = changedEngines.get(
|
|
this.#settings.metaData[prop]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#migrateTo11() {
|
|
// The keys of _iconMapObj are changed from width and height to width only.
|
|
// See bug 1655066.
|
|
if (this.#settings.version < 11 && this.#settings.engines) {
|
|
for (let engine of this.#settings.engines) {
|
|
if (!engine._iconMapObj) {
|
|
continue;
|
|
}
|
|
let oldIconMap = engine._iconMapObj;
|
|
engine._iconMapObj = {};
|
|
|
|
for (let [sizeStr, icon] of Object.entries(oldIconMap)) {
|
|
let sizeObj;
|
|
try {
|
|
sizeObj = JSON.parse(sizeStr);
|
|
} catch {}
|
|
if (
|
|
typeof sizeObj === "object" &&
|
|
"width" in sizeObj &&
|
|
parseInt(sizeObj.width) > 0 &&
|
|
sizeObj.width == sizeObj.height
|
|
) {
|
|
engine._iconMapObj[sizeObj.width] = icon;
|
|
} else if (typeof sizeObj === "number") {
|
|
// This happens if the user copies a version 11+ search config to
|
|
// an old install, which gets updated eventually; see bug 1940533.
|
|
engine._iconMapObj[sizeObj] = icon;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async #migrateTo12() {
|
|
// _iconURL is removed and its icon is stored in _iconMapObj instead.
|
|
// See bug 1655076.
|
|
if (this.#settings.version < 12 && this.#settings.engines) {
|
|
for (let engine of this.#settings.engines) {
|
|
if (engine._iconURL) {
|
|
let iconURL = engine._iconURL;
|
|
delete engine._iconURL;
|
|
|
|
let uri = lazy.SearchUtils.makeURI(iconURL);
|
|
if (!uri) {
|
|
continue;
|
|
}
|
|
|
|
// The URL should be either a data or moz-extension URL so this should
|
|
// always succeed and be fast. We skip other schemes just to be sure.
|
|
// If we fail to fetch or decode the icon, we assume it's 16x16.
|
|
switch (uri.scheme) {
|
|
case "moz-extension":
|
|
try {
|
|
await lazy.AddonManager.readyPromise;
|
|
} catch (e) {
|
|
if (e == "shutting down") {
|
|
throw new Error("Addon manager shutting down");
|
|
} else {
|
|
throw new Error("Addon manager failed");
|
|
}
|
|
}
|
|
break;
|
|
case "data":
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
let byteArray, contentType;
|
|
try {
|
|
[byteArray, contentType] = await lazy.SearchUtils.fetchIcon(uri);
|
|
} catch {
|
|
lazy.logConsole.warn(
|
|
`_iconURL migration: failed to load icon of search engine ${engine._name}.`
|
|
);
|
|
engine._iconMapObj ||= {};
|
|
engine._iconMapObj[16] = iconURL;
|
|
continue;
|
|
}
|
|
|
|
// MAX_ICON_SIZE is not enforced in some cases. In those cases, we
|
|
// rescale the icon to 32x32.
|
|
if (byteArray.length > lazy.SearchUtils.MAX_ICON_SIZE) {
|
|
try {
|
|
[byteArray, contentType] = lazy.SearchUtils.rescaleIcon(
|
|
byteArray,
|
|
contentType
|
|
);
|
|
let url =
|
|
"data:" + contentType + ";base64," + byteArray.toBase64();
|
|
|
|
engine._iconMapObj ||= {};
|
|
engine._iconMapObj[32] = url;
|
|
} catch {
|
|
lazy.logConsole.warn(
|
|
`_iconURL migration: failed to resize icon of search engine ${engine._name}.`
|
|
);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
let size = lazy.SearchUtils.decodeSize(byteArray, contentType, 16);
|
|
engine._iconMapObj ||= {};
|
|
engine._iconMapObj[size] = iconURL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|