Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
/**
* This module exports a provider that offers token alias engines.
*/
import {
UrlbarProvider,
UrlbarUtils,
} from "resource:///modules/UrlbarUtils.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.sys.mjs",
UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs",
});
/**
* Class used to create the provider.
*/
class ProviderTokenAliasEngines extends UrlbarProvider {
constructor() {
super();
this._engines = [];
}
/**
* Returns the name of this provider.
*
* @returns {string} the name of this provider.
*/
get name() {
return "TokenAliasEngines";
}
/**
* Returns the type of this provider.
*
* @returns {integer} one of the types from UrlbarUtils.PROVIDER_TYPE.*
*/
get type() {
return UrlbarUtils.PROVIDER_TYPE.HEURISTIC;
}
get PRIORITY() {
// Beats UrlbarProviderSearchSuggestions and UrlbarProviderPlaces.
return 1;
}
/**
* Whether this provider should be invoked for the given context.
* If this method returns false, the providers manager won't start a query
* with this provider, to save on resources.
*
* @param {UrlbarQueryContext} queryContext The query context object
* @returns {boolean} Whether this provider should be invoked for the search.
*/
async isActive(queryContext) {
let instance = this.queryInstance;
// This is usually reset on canceling or completing the query, but since we
// query in isActive, it may not have been canceled by the previous call.
// It is an object with values { result: UrlbarResult, instance: Query }.
this._autofillData = null;
// Once the user starts typing a search string after the token, we hand off
// suggestions to UrlbarProviderSearchSuggestions.
if (
!queryContext.searchString.startsWith("@") ||
queryContext.tokens.length != 1
) {
return false;
}
// Do not show token alias results in search mode.
if (queryContext.searchMode) {
return false;
}
this._engines = await lazy.UrlbarSearchUtils.tokenAliasEngines();
if (!this._engines.length) {
return false;
}
// Check the query was not canceled while this executed.
if (instance != this.queryInstance) {
return false;
}
if (queryContext.trimmedSearchString == "@") {
return true;
}
// If the user is typing a potential engine name, autofill it.
if (lazy.UrlbarPrefs.get("autoFill") && queryContext.allowAutofill) {
let result = await this._getAutofillResult(queryContext);
if (result && instance == this.queryInstance) {
this._autofillData = { result, instance };
return true;
}
}
return false;
}
/**
* Starts querying.
*
* @param {object} queryContext The query context object
* @param {Function} addCallback Callback invoked by the provider to add a new
* result.
*/
async startQuery(queryContext, addCallback) {
if (!this._engines || !this._engines.length) {
return;
}
if (
this._autofillData &&
this._autofillData.instance == this.queryInstance
) {
addCallback(this, this._autofillData.result);
}
let instance = this.queryInstance;
for (let { engine, tokenAliases } of this._engines) {
if (
tokenAliases[0].startsWith(queryContext.trimmedSearchString) &&
engine.name != this._autofillData?.result.payload.engine
) {
let result = new lazy.UrlbarResult(
UrlbarUtils.RESULT_TYPE.SEARCH,
UrlbarUtils.RESULT_SOURCE.SEARCH,
...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED],
keyword: [tokenAliases[0], UrlbarUtils.HIGHLIGHT.TYPED],
keywords: tokenAliases.join(", "),
query: ["", UrlbarUtils.HIGHLIGHT.TYPED],
icon: await engine.getIconURL(),
providesSearchMode: true,
})
);
if (instance != this.queryInstance) {
break;
}
addCallback(this, result);
}
}
this._autofillData = null;
}
/**
* Gets the provider's priority.
*
* @returns {number} The provider's priority for the given query.
*/
getPriority() {
return this.PRIORITY;
}
/**
* Cancels a running query.
*/
cancelQuery() {
if (this._autofillData?.instance == this.queryInstance) {
this._autofillData = null;
}
}
async _getAutofillResult(queryContext) {
let { lowerCaseSearchString } = queryContext;
// The user is typing a specific engine. We should show a heuristic result.
for (let { engine, tokenAliases } of this._engines) {
for (let alias of tokenAliases) {
if (alias.startsWith(lowerCaseSearchString)) {
// We found the engine.
// Stop adding an autofill result once the user has typed the full
// alias followed by a space. We enter search mode at that point.
if (
lowerCaseSearchString.startsWith(alias) &&
lazy.UrlbarTokenizer.REGEXP_SPACES_START.test(
lowerCaseSearchString.substring(alias.length)
)
) {
return null;
}
// Add an autofill result. Append a space so the user can hit enter
// or the right arrow key and immediately start typing their query.
let aliasPreservingUserCase =
queryContext.searchString +
alias.substr(queryContext.searchString.length);
let value = aliasPreservingUserCase + " ";
let result = new lazy.UrlbarResult(
UrlbarUtils.RESULT_TYPE.SEARCH,
UrlbarUtils.RESULT_SOURCE.SEARCH,
...lazy.UrlbarResult.payloadAndSimpleHighlights(
queryContext.tokens,
{
engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED],
keyword: [aliasPreservingUserCase, UrlbarUtils.HIGHLIGHT.TYPED],
keywords: tokenAliases.join(", "),
query: ["", UrlbarUtils.HIGHLIGHT.TYPED],
icon: await engine.getIconURL(),
providesSearchMode: true,
}
)
);
// We set suggestedIndex = 0 instead of the heuristic because we
// don't want this result to be automatically selected. That way,
// users can press Tab to select the result, building on their
// muscle memory from tab-to-search.
result.suggestedIndex = 0;
result.autofill = {
value,
selectionStart: queryContext.searchString.length,
selectionEnd: value.length,
};
return result;
}
}
}
return null;
}
}
export var UrlbarProviderTokenAliasEngines = new ProviderTokenAliasEngines();