311 lines
8.8 KiB
JavaScript
311 lines
8.8 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/. */
|
|
|
|
/* eslint no-shadow: error, mozilla/no-aArgs: error */
|
|
|
|
/**
|
|
* @typedef {import("./OpenSearchLoader.sys.mjs").OpenSearchProperties} OpenSearchProperties
|
|
*/
|
|
|
|
import {
|
|
EngineURL,
|
|
SearchEngine,
|
|
} from "moz-src:///toolkit/components/search/SearchEngine.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
loadAndParseOpenSearchEngine:
|
|
"moz-src:///toolkit/components/search/OpenSearchLoader.sys.mjs",
|
|
SearchUtils: "moz-src:///toolkit/components/search/SearchUtils.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
|
|
return console.createInstance({
|
|
prefix: "OpenSearchEngine",
|
|
maxLogLevel: lazy.SearchUtils.loggingEnabled ? "Debug" : "Warn",
|
|
});
|
|
});
|
|
|
|
// The default engine update interval, in days. This is only used if an engine
|
|
// specifies an updateURL, but not an updateInterval.
|
|
const OPENSEARCH_DEFAULT_UPDATE_INTERVAL = 7;
|
|
|
|
/**
|
|
* OpenSearchEngine represents an OpenSearch base search engine.
|
|
*/
|
|
export class OpenSearchEngine extends SearchEngine {
|
|
// The data describing the engine, in the form of an XML document element.
|
|
_data = null;
|
|
// The number of days between update checks for new versions
|
|
_updateInterval = null;
|
|
// The url to check at for a new update
|
|
_updateURL = null;
|
|
|
|
/**
|
|
* Creates a OpenSearchEngine.
|
|
*
|
|
* @param {object} [options]
|
|
* The options object
|
|
* @param {object} [options.json]
|
|
* An object that represents the saved JSON settings for the engine.
|
|
* @param {OpenSearchProperties} [options.engineData]
|
|
* The engine data for this search engine that will have been loaded via
|
|
* `OpenSearchLoader`.
|
|
* @param {string} [options.faviconURL]
|
|
* The website favicon, to be used if the engine data hasn't specified an
|
|
* icon.
|
|
*/
|
|
constructor(options = {}) {
|
|
super({
|
|
loadPath:
|
|
options.json?._loadPath ??
|
|
OpenSearchEngine.getAnonymizedLoadPath(
|
|
lazy.SearchUtils.sanitizeName(options.engineData.name),
|
|
options.engineData.installURL
|
|
),
|
|
});
|
|
|
|
if (options.faviconURL) {
|
|
this._setIcon(options.faviconURL, undefined, false).catch(e =>
|
|
lazy.logConsole.error(
|
|
`Error while setting icon for search engine ${options.engineData.name}:`,
|
|
e.message
|
|
)
|
|
);
|
|
}
|
|
|
|
if (options.engineData) {
|
|
this.#setEngineData(options.engineData);
|
|
|
|
// As this is a new engine, we must set the verification hash for the load
|
|
// path set in the constructor.
|
|
this.setAttr(
|
|
"loadPathHash",
|
|
lazy.SearchUtils.getVerificationHash(this._loadPath)
|
|
);
|
|
|
|
if (this.hasUpdates) {
|
|
this.#setNextUpdateTime();
|
|
}
|
|
} else {
|
|
this._initWithJSON(options.json);
|
|
this._updateInterval = options.json._updateInterval ?? null;
|
|
this._updateURL = options.json._updateURL ?? null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a JavaScript object that represents this engine.
|
|
*
|
|
* @returns {object}
|
|
* An object suitable for serialization as JSON.
|
|
*/
|
|
toJSON() {
|
|
let json = super.toJSON();
|
|
json._updateInterval = this._updateInterval;
|
|
json._updateURL = this._updateURL;
|
|
return json;
|
|
}
|
|
|
|
/**
|
|
* Determines if this search engine has updates url.
|
|
*
|
|
* @returns {boolean}
|
|
* Returns true if this search engine may update itself.
|
|
*/
|
|
get hasUpdates() {
|
|
// Whether or not the engine has an update URL
|
|
let selfURL = this._getURLOfType(
|
|
lazy.SearchUtils.URL_TYPE.OPENSEARCH,
|
|
"self"
|
|
);
|
|
return !!(this._updateURL || selfURL);
|
|
}
|
|
|
|
/**
|
|
* Returns the engine's updateURI if it exists and returns null otherwise
|
|
*
|
|
* @returns {?nsIURI}
|
|
*/
|
|
get updateURI() {
|
|
let updateURL = this._getURLOfType(lazy.SearchUtils.URL_TYPE.OPENSEARCH);
|
|
let updateURI =
|
|
updateURL && updateURL._hasRelation("self")
|
|
? updateURL.getSubmission("", this.queryCharset).uri
|
|
: lazy.SearchUtils.makeURI(this._updateURL);
|
|
return updateURI;
|
|
}
|
|
|
|
/**
|
|
* Considers if this engine needs to be updated, and updates it if necessary.
|
|
*/
|
|
async maybeUpdate() {
|
|
if (!this.hasUpdates) {
|
|
return;
|
|
}
|
|
|
|
let currentTime = Date.now();
|
|
|
|
let expireTime = this.getAttr("updateexpir");
|
|
|
|
if (!expireTime || !(expireTime <= currentTime)) {
|
|
lazy.logConsole.debug(this.name, "Skipping update, not expired yet.");
|
|
return;
|
|
}
|
|
|
|
await this.#update();
|
|
|
|
this.#setNextUpdateTime();
|
|
}
|
|
|
|
/**
|
|
* Updates the OpenSearch engine details from the server.
|
|
*/
|
|
async #update() {
|
|
let updateURI = this.updateURI;
|
|
if (updateURI) {
|
|
let data = await lazy.loadAndParseOpenSearchEngine(
|
|
updateURI,
|
|
this.getAttr("updatelastmodified")
|
|
);
|
|
|
|
this.#setEngineData(data);
|
|
|
|
lazy.SearchUtils.notifyAction(
|
|
this,
|
|
lazy.SearchUtils.MODIFIED_TYPE.CHANGED
|
|
);
|
|
|
|
// Keep track of the last modified date, so that we can make conditional
|
|
// server requests for future updates.
|
|
this.setAttr("updatelastmodified", new Date().toUTCString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the data for this engine based on the OpenSearch properties.
|
|
*
|
|
* @param {OpenSearchProperties} data
|
|
* The OpenSearch data.
|
|
*/
|
|
#setEngineData(data) {
|
|
let name = data.name.trim();
|
|
if (Services.search.getEngineByName(name)) {
|
|
throw Components.Exception(
|
|
"Found a duplicate engine",
|
|
Ci.nsISearchService.ERROR_DUPLICATE_ENGINE
|
|
);
|
|
}
|
|
|
|
this._name = name;
|
|
this._queryCharset = data.queryCharset ?? "UTF-8";
|
|
if (data.searchForm) {
|
|
try {
|
|
let searchFormUrl = new EngineURL(
|
|
lazy.SearchUtils.URL_TYPE.SEARCH_FORM,
|
|
"GET",
|
|
data.searchForm
|
|
);
|
|
this._urls.push(searchFormUrl);
|
|
} catch (ex) {
|
|
throw Components.Exception(
|
|
`Failed to add ${data.searchForm} as a searchForm URL`,
|
|
Cr.NS_ERROR_FAILURE
|
|
);
|
|
}
|
|
}
|
|
|
|
for (let url of data.urls) {
|
|
// Some Mozilla provided opensearch engines used to specify their searchForm
|
|
// through a Url with rel="searchform". We add these as URLs with type searchform.
|
|
if (url.rels.includes("searchform")) {
|
|
let searchFormURL;
|
|
try {
|
|
searchFormURL = new EngineURL(
|
|
lazy.SearchUtils.URL_TYPE.SEARCH_FORM,
|
|
"GET",
|
|
url.template
|
|
);
|
|
} catch (ex) {
|
|
throw Components.Exception(
|
|
`Failed to add ${url.template} as an Engine URL`,
|
|
Cr.NS_ERROR_FAILURE
|
|
);
|
|
}
|
|
this.#addParamsToUrl(searchFormURL, url.params);
|
|
this._urls.push(searchFormURL);
|
|
}
|
|
|
|
let engineURL;
|
|
try {
|
|
engineURL = new EngineURL(url.type, url.method, url.template);
|
|
} catch (ex) {
|
|
throw Components.Exception(
|
|
`Failed to add ${url.template} as an Engine URL`,
|
|
Cr.NS_ERROR_FAILURE
|
|
);
|
|
}
|
|
|
|
let nonSearchformRels = url.rels.filter(rel => rel != "searchform");
|
|
if (nonSearchformRels.length) {
|
|
engineURL.rels = nonSearchformRels;
|
|
}
|
|
|
|
this.#addParamsToUrl(engineURL, url.params);
|
|
this._urls.push(engineURL);
|
|
}
|
|
|
|
for (let image of data.images) {
|
|
this._setIcon(image.url, image.size).catch(e =>
|
|
lazy.logConsole.error(
|
|
`Error while setting icon for search engine ${data.name}:`,
|
|
e.message
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper method to add all params to the given EngineURL,
|
|
* ignoring those params with missing name or value.
|
|
*
|
|
* @param {EngineURL} engineURL the EngineURL to add the params to.
|
|
* @param {Array} params param objects with name and value properties.
|
|
*/
|
|
#addParamsToUrl(engineURL, params) {
|
|
for (let param of params) {
|
|
try {
|
|
engineURL.addParam(param.name, param.value);
|
|
} catch (ex) {
|
|
// Ignore failure
|
|
lazy.logConsole.error("OpenSearch url has an invalid param", param);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the next update time for this engine.
|
|
*/
|
|
#setNextUpdateTime() {
|
|
var interval = this._updateInterval || OPENSEARCH_DEFAULT_UPDATE_INTERVAL;
|
|
var milliseconds = interval * 86400000; // |interval| is in days
|
|
this.setAttr("updateexpir", Date.now() + milliseconds);
|
|
}
|
|
|
|
/**
|
|
* This indicates where we found the .xml file to load the engine,
|
|
* and attempts to hide user-identifiable data (such as username).
|
|
*
|
|
* @param {string} sanitizedName
|
|
* The sanitized name of the engine.
|
|
* @param {nsIURI} uri
|
|
* The uri the engine was loaded from.
|
|
* @returns {string}
|
|
* A load path with reduced data.
|
|
*/
|
|
static getAnonymizedLoadPath(sanitizedName, uri) {
|
|
return `[${uri.scheme}]${uri.host}/${sanitizedName}.xml`;
|
|
}
|
|
}
|