191 lines
5.4 KiB
JavaScript
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();
|