Files
tubestation/browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs

191 lines
5.4 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/. */
import { UrlbarUtils } from "resource:///modules/UrlbarUtils.sys.mjs";
import {
ActionsProvider,
ActionsResult,
} from "resource:///modules/ActionsProvider.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
OpenSearchEngine: "resource://gre/modules/OpenSearchEngine.sys.mjs",
loadAndParseOpenSearchEngine:
"resource://gre/modules/OpenSearchLoader.sys.mjs",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.sys.mjs",
});
const ENABLED_PREF = "contextualSearch.enabled";
const INSTALLED_ENGINE = "installed-engine";
const OPEN_SEARCH_ENGINE = "opensearch-engine";
const CONTEXTUAL_SEARCH_ENGINE = "contextual-search-engine";
/**
* A provider that returns an option for using the search engine provided
* by the active view if it utilizes OpenSearch.
*/
class ProviderContextualSearch extends ActionsProvider {
constructor() {
super();
this.engines = new Map();
}
get name() {
return "ActionsProviderContextualSearch";
}
isActive(queryContext) {
return (
queryContext.trimmedSearchString &&
lazy.UrlbarPrefs.get(ENABLED_PREF) &&
!queryContext.searchMode
);
}
async queryActions(queryContext) {
let instance = this.queryInstance;
const hostname = URL.parse(queryContext.currentPage)?.hostname;
// This happens on about pages, which won't have associated engines
if (!hostname) {
return null;
}
let { engine } = await this.fetchEngineDetails();
let icon = engine?.icon || (await engine?.getIconURL?.());
let defaultEngine = lazy.UrlbarSearchUtils.getDefaultEngine();
if (
!engine ||
engine.name === defaultEngine?.name ||
instance != this.queryInstance
) {
return null;
}
return [
new ActionsResult({
key: "contextual-search",
l10nId: "urlbar-result-search-with",
l10nArgs: { engine: engine.name || engine.title },
icon,
onPick: (context, controller) => {
this.pickAction(context, controller);
},
}),
];
}
async fetchEngineDetails() {
let browser =
lazy.BrowserWindowTracker.getTopWindow().gBrowser.selectedBrowser;
let hostname;
try {
// currentURI.host will throw on pages without a host ("about:" pages).
hostname = browser.currentURI.host;
} catch (e) {
return null;
}
if (this.engines.has(hostname)) {
return { type: INSTALLED_ENGINE, engine: this.engines.get(hostname) };
}
// Strip www. to allow for partial matches when looking for an engine.
const [host] = UrlbarUtils.stripPrefixAndTrim(hostname, {
stripWww: true,
});
let engines = await lazy.UrlbarSearchUtils.enginesForDomainPrefix(host, {
matchAllDomainLevels: true,
});
if (engines.length) {
return { type: INSTALLED_ENGINE, engine: engines[0] };
}
let contextualEngineConfig =
await Services.search.findContextualSearchEngineByHost(host);
if (contextualEngineConfig) {
return {
type: CONTEXTUAL_SEARCH_ENGINE,
engine: contextualEngineConfig,
};
}
if (browser?.engines?.length) {
return { type: OPEN_SEARCH_ENGINE, engine: browser.engines[0] };
}
return {};
}
async pickAction(queryContext, controller, _element) {
let { type, engine } = await this.fetchEngineDetails();
let enterSeachMode = true;
let engineObj;
if (
(type == CONTEXTUAL_SEARCH_ENGINE || type == OPEN_SEARCH_ENGINE) &&
!queryContext.isPrivate
) {
engineObj = await this.#installEngine({ type, engine }, controller);
} else if (type == OPEN_SEARCH_ENGINE) {
let openSearchEngineData = await lazy.loadAndParseOpenSearchEngine(
Services.io.newURI(engine.uri)
);
engineObj = new lazy.OpenSearchEngine({
engineData: openSearchEngineData,
});
enterSeachMode = false;
} else if (type == INSTALLED_ENGINE || type == CONTEXTUAL_SEARCH_ENGINE) {
engineObj = engine;
}
this.#performSearch(
engineObj,
queryContext.searchString,
controller.input,
enterSeachMode
);
}
async #installEngine({ type, engine }, controller) {
let engineObj;
if (type == CONTEXTUAL_SEARCH_ENGINE) {
await Services.search.addContextualSearchEngine(engine);
engineObj = engine;
} else {
engineObj = await Services.search.addOpenSearchEngine(
engine.uri,
engine.icon,
controller.input.browsingContext
);
}
engineObj.setAttr("auto-installed", true);
return engineObj;
}
async #performSearch(engine, search, input, enterSearchMode) {
const [url] = UrlbarUtils.getSearchQueryUrl(engine, search);
if (enterSearchMode) {
input.search(search, { searchEngine: engine });
}
input.window.gBrowser.fixupAndLoadURIString(url, {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
input.window.gBrowser.selectedBrowser.focus();
}
resetForTesting() {
this.engines = new Map();
}
}
export var ActionsProviderContextualSearch = new ProviderContextualSearch();