2020-06-17 00:05:14 +03:00
|
|
|
/* 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/. */
|
|
|
|
|
2020-09-11 18:53:25 +03:00
|
|
|
var EXPORTED_SYMBOLS = ["SearchSettings"];
|
2020-06-17 00:05:14 +03:00
|
|
|
|
|
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
|
|
);
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
|
|
DeferredTask: "resource://gre/modules/DeferredTask.jsm",
|
|
|
|
SearchUtils: "resource://gre/modules/SearchUtils.jsm",
|
|
|
|
Services: "resource://gre/modules/Services.jsm",
|
|
|
|
});
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "logConsole", () => {
|
|
|
|
return console.createInstance({
|
2020-09-11 18:53:25 +03:00
|
|
|
prefix: "SearchSettings",
|
2020-06-17 00:05:14 +03:00
|
|
|
maxLogLevel: SearchUtils.loggingEnabled ? "Debug" : "Warn",
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-09-11 18:53:25 +03:00
|
|
|
const SETTINGS_FILENAME = "search.json.mozlz4";
|
2020-06-17 00:05:14 +03:00
|
|
|
|
|
|
|
/**
|
2020-09-11 18:53:25 +03:00
|
|
|
* This class manages the saves search settings.
|
2020-06-17 00:05:14 +03:00
|
|
|
*
|
2020-09-11 18:53:25 +03:00
|
|
|
* Global settings can be saved and obtained from this class via the
|
2020-06-17 00:05:14 +03:00
|
|
|
* `*Attribute` methods.
|
|
|
|
*/
|
2020-09-11 18:53:25 +03:00
|
|
|
class SearchSettings {
|
2020-06-17 00:05:14 +03:00
|
|
|
constructor(searchService) {
|
|
|
|
this._searchService = searchService;
|
|
|
|
}
|
2020-06-30 10:45:22 +03:00
|
|
|
|
|
|
|
QueryInterface = ChromeUtils.generateQI([Ci.nsIObserver]);
|
|
|
|
|
2020-09-11 18:53:25 +03:00
|
|
|
// Delay for batching invalidation of the JSON settings (ms)
|
|
|
|
static SETTINGS_INVALIDATION_DELAY = 1000;
|
2020-06-30 10:45:22 +03:00
|
|
|
|
2020-06-17 00:05:14 +03:00
|
|
|
/**
|
|
|
|
* A reference to the pending DeferredTask, if there is one.
|
|
|
|
*/
|
|
|
|
_batchTask = null;
|
|
|
|
|
|
|
|
/**
|
2020-09-11 18:53:25 +03:00
|
|
|
* The current metadata stored in the settings. This stores:
|
2020-06-17 00:05:14 +03:00
|
|
|
* - current
|
|
|
|
* The current user-set default engine. The associated hash is called
|
|
|
|
* 'hash'.
|
|
|
|
* - private
|
|
|
|
* The current user-set private engine. The associated hash is called
|
|
|
|
* 'privateHash'.
|
|
|
|
*
|
2020-09-01 21:08:46 +03:00
|
|
|
* All of the above have associated hash fields to validate the value is set
|
|
|
|
* by the application.
|
2020-06-17 00:05:14 +03:00
|
|
|
*/
|
|
|
|
_metaData = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A reference to the search service so that we can save the engines list.
|
|
|
|
*/
|
2020-09-17 22:42:29 +03:00
|
|
|
_searchService = null;
|
2020-06-17 00:05:14 +03:00
|
|
|
|
2020-12-04 03:01:23 +03:00
|
|
|
/*
|
|
|
|
* A copy of the settings so we can persist metadata for engines that
|
|
|
|
* are not currently active.
|
|
|
|
*/
|
|
|
|
_currentSettings = null;
|
|
|
|
|
2020-06-30 10:45:22 +03:00
|
|
|
addObservers() {
|
|
|
|
Services.obs.addObserver(this, SearchUtils.TOPIC_ENGINE_MODIFIED);
|
|
|
|
Services.obs.addObserver(this, SearchUtils.TOPIC_SEARCH_SERVICE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleans up, removing observers.
|
|
|
|
*/
|
|
|
|
removeObservers() {
|
|
|
|
Services.obs.removeObserver(this, SearchUtils.TOPIC_ENGINE_MODIFIED);
|
|
|
|
Services.obs.removeObserver(this, SearchUtils.TOPIC_SEARCH_SERVICE);
|
|
|
|
}
|
|
|
|
|
2020-06-17 00:05:14 +03:00
|
|
|
/**
|
2020-09-11 18:53:25 +03:00
|
|
|
* Reads the settings file.
|
2020-06-17 00:05:14 +03:00
|
|
|
*
|
2020-06-30 10:45:22 +03:00
|
|
|
* @param {string} origin
|
2020-09-11 18:53:25 +03:00
|
|
|
* 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.
|
2020-06-17 00:05:14 +03:00
|
|
|
* @returns {object}
|
2020-09-11 18:53:25 +03:00
|
|
|
* Returns the settings file data.
|
2020-06-17 00:05:14 +03:00
|
|
|
*/
|
2020-06-30 10:45:22 +03:00
|
|
|
async get(origin = "") {
|
2020-06-17 00:05:14 +03:00
|
|
|
let json;
|
2020-06-30 10:45:22 +03:00
|
|
|
await this._ensurePendingWritesCompleted(origin);
|
2020-06-17 00:05:14 +03:00
|
|
|
try {
|
2021-03-25 00:07:18 +03:00
|
|
|
let settingsFilePath = PathUtils.join(
|
|
|
|
await PathUtils.getProfileDir(),
|
2020-09-11 18:53:25 +03:00
|
|
|
SETTINGS_FILENAME
|
2020-06-17 00:05:14 +03:00
|
|
|
);
|
2021-03-25 00:07:18 +03:00
|
|
|
json = await IOUtils.readJSON(settingsFilePath, { decompress: true });
|
2020-06-17 00:05:14 +03:00
|
|
|
if (!json.engines || !json.engines.length) {
|
|
|
|
throw new Error("no engine in the file");
|
|
|
|
}
|
|
|
|
} catch (ex) {
|
2020-09-17 22:42:31 +03:00
|
|
|
logConsole.warn("get: No settings file exists, new profile?", ex);
|
2020-06-17 00:05:14 +03:00
|
|
|
json = {};
|
|
|
|
}
|
|
|
|
if (json.metaData) {
|
|
|
|
this._metaData = json.metaData;
|
|
|
|
}
|
2020-09-11 22:56:39 +03:00
|
|
|
// Versions of gecko older than 82 stored the order flag as a preference.
|
|
|
|
// This was changed in version 6 of the settings file.
|
|
|
|
if (json.version < 6 || !("useSavedOrder" in this._metaData)) {
|
|
|
|
const prefName = SearchUtils.BROWSER_SEARCH_PREF + "useDBForOrder";
|
|
|
|
let useSavedOrder = Services.prefs.getBoolPref(prefName, false);
|
|
|
|
|
|
|
|
this.setAttribute("useSavedOrder", useSavedOrder);
|
|
|
|
|
|
|
|
// Clear the old pref so it isn't lying around.
|
|
|
|
Services.prefs.clearUserPref(prefName);
|
|
|
|
}
|
2020-06-17 00:05:14 +03:00
|
|
|
|
2020-12-04 03:01:23 +03:00
|
|
|
this._currentSettings = json;
|
2020-06-17 00:05:14 +03:00
|
|
|
return json;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-09-11 18:53:25 +03:00
|
|
|
* Queues writing the settings until after SETTINGS_INVALIDATION_DELAY. If there
|
2020-06-17 00:05:14 +03:00
|
|
|
* is a currently queued task then it will be restarted.
|
|
|
|
*/
|
2020-06-30 10:45:22 +03:00
|
|
|
_delayedWrite() {
|
2020-06-17 00:05:14 +03:00
|
|
|
if (this._batchTask) {
|
|
|
|
this._batchTask.disarm();
|
|
|
|
} else {
|
|
|
|
let task = async () => {
|
2020-09-17 22:42:29 +03:00
|
|
|
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;
|
|
|
|
}
|
2020-09-11 18:53:25 +03:00
|
|
|
logConsole.debug("batchTask: Invalidating engine settings");
|
2020-06-30 10:45:22 +03:00
|
|
|
await this._write();
|
2020-06-17 00:05:14 +03:00
|
|
|
};
|
2020-07-16 16:19:05 +03:00
|
|
|
this._batchTask = new DeferredTask(
|
|
|
|
task,
|
2020-09-11 18:53:25 +03:00
|
|
|
SearchSettings.SETTINGS_INVALIDATION_DELAY
|
2020-07-16 16:19:05 +03:00
|
|
|
);
|
2020-06-17 00:05:14 +03:00
|
|
|
}
|
|
|
|
this._batchTask.arm();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-09-11 18:53:25 +03:00
|
|
|
* Ensures any pending writes of the settings are completed.
|
2020-06-17 00:05:14 +03:00
|
|
|
*
|
|
|
|
* @param {string} origin
|
2020-09-11 18:53:25 +03:00
|
|
|
* 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.
|
2020-06-17 00:05:14 +03:00
|
|
|
*/
|
2020-06-30 10:45:22 +03:00
|
|
|
async _ensurePendingWritesCompleted(origin = "") {
|
2020-09-11 18:53:25 +03:00
|
|
|
// Before we read the settings file, first make sure all pending tasks are clear.
|
2020-06-30 10:45:22 +03:00
|
|
|
if (!this._batchTask) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
logConsole.debug("finalizing batch task");
|
|
|
|
let task = this._batchTask;
|
|
|
|
this._batchTask = null;
|
2020-09-11 18:53:25 +03:00
|
|
|
// Tests manipulate the settings directly, so let's not double-write with
|
|
|
|
// stale settings data here.
|
2020-06-30 10:45:22 +03:00
|
|
|
if (origin == "test") {
|
|
|
|
task.disarm();
|
|
|
|
} else {
|
|
|
|
await task.finalize();
|
2020-06-17 00:05:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-09-11 18:53:25 +03:00
|
|
|
* Writes the settings to disk (no delay).
|
2020-06-17 00:05:14 +03:00
|
|
|
*/
|
2020-06-30 10:45:22 +03:00
|
|
|
async _write() {
|
2020-06-17 00:05:14 +03:00
|
|
|
if (this._batchTask) {
|
|
|
|
this._batchTask.disarm();
|
|
|
|
}
|
|
|
|
|
2020-09-11 18:53:25 +03:00
|
|
|
let settings = {};
|
2020-06-17 00:05:14 +03:00
|
|
|
|
2020-09-11 18:53:25 +03:00
|
|
|
// Allows us to force a settings refresh should the settings format change.
|
|
|
|
settings.version = SearchUtils.SETTINGS_VERSION;
|
|
|
|
settings.engines = [...this._searchService._engines.values()];
|
|
|
|
settings.metaData = this._metaData;
|
2020-06-17 00:05:14 +03:00
|
|
|
|
2020-12-04 03:01:23 +03:00
|
|
|
// 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._currentSettings?.engines) {
|
|
|
|
for (let engine of this._currentSettings.engines) {
|
|
|
|
let included = settings.engines.some(e => e._name == engine._name);
|
|
|
|
if (engine._isAppProvided && !included) {
|
|
|
|
settings.engines.push(engine);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the local copy.
|
|
|
|
this._currentSettings = settings;
|
|
|
|
|
2020-06-17 00:05:14 +03:00
|
|
|
try {
|
2020-09-11 18:53:25 +03:00
|
|
|
if (!settings.engines.length) {
|
2020-06-17 00:05:14 +03:00
|
|
|
throw new Error("cannot write without any engine.");
|
|
|
|
}
|
|
|
|
|
2020-09-11 18:53:25 +03:00
|
|
|
logConsole.debug("_write: Writing to settings file.");
|
2021-03-25 00:07:18 +03:00
|
|
|
let path = PathUtils.join(
|
|
|
|
await PathUtils.getProfileDir(),
|
|
|
|
SETTINGS_FILENAME
|
|
|
|
);
|
|
|
|
await IOUtils.writeJSON(path, settings, { compress: true });
|
2020-09-11 18:53:25 +03:00
|
|
|
logConsole.debug("_write: settings file written to disk.");
|
2020-06-17 00:05:14 +03:00
|
|
|
Services.obs.notifyObservers(
|
|
|
|
null,
|
|
|
|
SearchUtils.TOPIC_SEARCH_SERVICE,
|
2020-09-11 18:53:25 +03:00
|
|
|
"write-settings-to-disk-complete"
|
2020-06-17 00:05:14 +03:00
|
|
|
);
|
|
|
|
} catch (ex) {
|
2020-09-11 18:53:25 +03:00
|
|
|
logConsole.error("_write: Could not write to settings file:", ex);
|
2020-06-17 00:05:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-11 22:56:39 +03:00
|
|
|
/**
|
|
|
|
* Sets an attribute without verification.
|
|
|
|
*
|
|
|
|
* @param {string} name
|
|
|
|
* The name of the attribute to set.
|
|
|
|
* @param {*} val
|
|
|
|
* The value to set.
|
|
|
|
*/
|
|
|
|
setAttribute(name, val) {
|
|
|
|
this._metaData[name] = val;
|
|
|
|
this._delayedWrite();
|
|
|
|
}
|
|
|
|
|
2020-06-17 00:05:14 +03:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
setVerifiedAttribute(name, val) {
|
2020-06-30 10:45:22 +03:00
|
|
|
this._metaData[name] = val;
|
2020-07-03 13:01:02 +03:00
|
|
|
this._metaData[this.getHashName(name)] = SearchUtils.getVerificationHash(
|
|
|
|
val
|
|
|
|
);
|
2020-06-30 10:45:22 +03:00
|
|
|
this._delayedWrite();
|
2020-06-17 00:05:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-09-03 01:24:39 +03:00
|
|
|
* Gets an attribute without verification.
|
2020-06-17 00:05:14 +03:00
|
|
|
*
|
|
|
|
* @param {string} name
|
|
|
|
* The name of the attribute to get.
|
|
|
|
* @returns {*}
|
|
|
|
* The value of the attribute, or undefined if not known.
|
|
|
|
*/
|
|
|
|
getAttribute(name) {
|
2020-09-11 22:56:39 +03:00
|
|
|
return this._metaData[name] ?? undefined;
|
2020-06-17 00:05:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a verified attribute.
|
|
|
|
*
|
|
|
|
* @param {string} name
|
|
|
|
* The name of the attribute to get.
|
|
|
|
* @returns {*}
|
|
|
|
* The value of the attribute, or undefined if not known or an empty strings
|
|
|
|
* if it does not match the verification hash.
|
|
|
|
*/
|
|
|
|
getVerifiedAttribute(name) {
|
|
|
|
let val = this.getAttribute(name);
|
2020-06-10 20:11:12 +03:00
|
|
|
if (
|
|
|
|
val &&
|
2020-07-03 13:01:02 +03:00
|
|
|
this.getAttribute(this.getHashName(name)) !=
|
|
|
|
SearchUtils.getVerificationHash(val)
|
2020-06-10 20:11:12 +03:00
|
|
|
) {
|
2020-06-17 00:05:14 +03:00
|
|
|
logConsole.warn("getVerifiedGlobalAttr, invalid hash for", name);
|
2020-09-03 01:24:39 +03:00
|
|
|
return undefined;
|
2020-06-17 00:05:14 +03:00
|
|
|
}
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2020-06-10 20:11:12 +03:00
|
|
|
/**
|
|
|
|
* Returns the name for the hash for a particular attribute. This is
|
|
|
|
* necessary because the normal default engine 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) {
|
|
|
|
if (name == "current") {
|
|
|
|
return "hash";
|
|
|
|
}
|
|
|
|
return name + "Hash";
|
|
|
|
}
|
|
|
|
|
2020-06-17 00:05:14 +03:00
|
|
|
/**
|
2020-09-11 18:53:25 +03:00
|
|
|
* Handles shutdown; writing the settings if necessary.
|
2020-06-17 00:05:14 +03:00
|
|
|
*
|
|
|
|
* @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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-30 10:45:22 +03:00
|
|
|
|
|
|
|
// nsIObserver
|
|
|
|
observe(engine, topic, verb) {
|
|
|
|
switch (topic) {
|
|
|
|
case SearchUtils.TOPIC_ENGINE_MODIFIED:
|
|
|
|
switch (verb) {
|
|
|
|
case SearchUtils.MODIFIED_TYPE.ADDED:
|
|
|
|
case SearchUtils.MODIFIED_TYPE.CHANGED:
|
|
|
|
case SearchUtils.MODIFIED_TYPE.REMOVED:
|
|
|
|
this._delayedWrite();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SearchUtils.TOPIC_SEARCH_SERVICE:
|
|
|
|
switch (verb) {
|
|
|
|
case "init-complete":
|
|
|
|
case "engines-reloaded":
|
|
|
|
this._delayedWrite();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-06-17 00:05:14 +03:00
|
|
|
}
|