fune/browser/components/urlbar/UrlbarSearchUtils.jsm

143 lines
4.1 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/. */
/*
* Search service utilities for urlbar. The only reason these functions aren't
* a part of UrlbarUtils is that we want O(1) case-insensitive lookup for search
* aliases, and to do that we need to observe the search service, persistent
* state, and an init method. A separate object is easier.
*/
"use strict";
var EXPORTED_SYMBOLS = ["UrlbarSearchUtils"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
/**
* Search service utilities for urlbar.
*/
class SearchUtils {
constructor() {
this._refreshEnginesByAliasPromise = Promise.resolve();
this.QueryInterface = ChromeUtils.generateQI([
"nsIObserver",
"nsISupportsWeakReference",
]);
}
/**
* Initializes the instance and also Services.search.
*/
async init() {
if (!this._initPromise) {
this._initPromise = this._initInternal();
}
await this._initPromise;
}
/**
* Gets the engine whose domain matches a given prefix.
*
* @param {string} prefix
* String containing the first part of the matching domain name.
* @returns {nsISearchEngine}
* The matching engine or null if there isn't one.
*/
async engineForDomainPrefix(prefix) {
await this.init();
for (let engine of await Services.search.getVisibleEngines()) {
let domain = engine.getResultDomain();
if (domain.startsWith(prefix) || domain.startsWith("www." + prefix)) {
return engine;
}
}
return null;
}
/**
* Gets the engine with a given alias.
*
* @param {string} alias
* A search engine alias. The alias string comparison is case insensitive.
* @returns {nsISearchEngine}
* The matching engine or null if there isn't one.
*/
async engineForAlias(alias) {
await Promise.all([this.init(), this._refreshEnginesByAliasPromise]);
return this._enginesByAlias.get(alias.toLocaleLowerCase()) || null;
}
/**
* Gets the aliases given search engine.
*
* @param {nsISearchEngine} engine
* @returns {array}
* An array of strings of the engine's aliases.
*/
aliasesForEngine(engine) {
let aliases = [];
if (engine.alias) {
aliases.push(engine.alias);
}
aliases.push(...engine.wrappedJSObject._internalAliases);
return aliases;
}
/**
* The list of engines with token ("@") aliases.
*
* @returns {array}
* Array of objects { engine, tokenAliases } for token alias engines.
*/
async tokenAliasEngines() {
await this.init();
let tokenAliasEngines = [];
for (let engine of await Services.search.getVisibleEngines()) {
let tokenAliases = this.aliasesForEngine(engine).filter(a =>
a.startsWith("@")
);
if (tokenAliases.length) {
tokenAliasEngines.push({ engine, tokenAliases });
}
}
return tokenAliasEngines;
}
async _initInternal() {
await Services.search.init();
await this._refreshEnginesByAlias();
Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, true);
}
async _refreshEnginesByAlias() {
// See the comment at the top of this file. The only reason we need this
// class is for O(1) case-insensitive lookup for search aliases, which is
// facilitated by _enginesByAlias.
this._enginesByAlias = new Map();
for (let engine of await Services.search.getVisibleEngines()) {
if (!engine.hidden) {
let aliases = this.aliasesForEngine(engine);
for (let alias of aliases) {
this._enginesByAlias.set(alias.toLocaleLowerCase(), engine);
}
}
}
}
observe(subject, topic, data) {
switch (data) {
case "engine-added":
case "engine-changed":
case "engine-removed":
case "engine-default":
this._refreshEnginesByAliasPromise = this._refreshEnginesByAlias();
break;
}
}
}
var UrlbarSearchUtils = new SearchUtils();