1
0
Fork 0
firefox/browser/components/urlbar/UrlbarProviderSemanticHistorySearch.sys.mjs
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

215 lines
6.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 search history suggestions
* based on embeddings and semantic search techniques using semantic
* history
*/
import {
UrlbarProvider,
UrlbarUtils,
} from "resource:///modules/UrlbarUtils.sys.mjs";
import { PlacesSemanticHistoryManager } from "resource://gre/modules/PlacesSemanticHistoryManager.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
EnrollmentType: "resource://nimbus/ExperimentAPI.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "logger", function () {
return UrlbarUtils.getLogger({ prefix: "SemanticHistorySearch" });
});
/**
* Class representing the Semantic History Search provider for the URL bar.
*
* This provider queries a semantic database created using history.
* It performs semantic search using embeddings generated
* by an ML model and retrieves results ranked by cosine similarity to the
* query's embedding.
*
* @class
*/
class ProviderSemanticHistorySearch extends UrlbarProvider {
#semanticManager;
#exposureRecorded;
/**
* Lazily creates (on first call) and returns the
* {@link PlacesSemanticHistoryManager} instance backing this provider.
*
* The manager is instantiated only once and cached in the private
* `#semanticManager` field. It is configured with sensible defaults for
* semantic history search:
* • `embeddingSize`: 384 dimensionality of vector embeddings
* • `rowLimit`: 10000 maximum rows pulled from Places
* • `samplingAttrib`: "frecency" column used when down-sampling
* • `changeThresholdCount`: 3 restart inference after this many DB changes
* • `distanceThreshold`: 0.75 cosine-distance cut-off for matches
*
* @returns {PlacesSemanticHistoryManager}
* The shared, initialized semantic-history manager instance.
*/
ensureSemanticManagerInitialized() {
if (!this.#semanticManager) {
const distanceThreshold = Services.prefs.getFloatPref(
"places.semanticHistory.distanceThreshold",
0.75
);
this.#semanticManager = new PlacesSemanticHistoryManager({
embeddingSize: 384,
rowLimit: 10000,
samplingAttrib: "frecency",
changeThresholdCount: 3,
distanceThreshold,
});
}
return this.#semanticManager;
}
get name() {
return "SemanticHistorySearch";
}
/**
* @returns {Values<typeof UrlbarUtils.PROVIDER_TYPE>}
*/
get type() {
return UrlbarUtils.PROVIDER_TYPE.PROFILE;
}
/**
* Determines if the provider is active for the given query context.
*
* @param {object} queryContext
* The context of the query, including the search string.
*/
async isActive(queryContext) {
const minSearchStringLength = lazy.UrlbarPrefs.get(
"suggest.semanticHistory.minLength"
);
if (
lazy.UrlbarPrefs.get("suggest.history") &&
queryContext.searchString.length >= minSearchStringLength &&
(!queryContext.searchMode ||
queryContext.searchMode.source == UrlbarUtils.RESULT_SOURCE.HISTORY)
) {
const semanticManager = this.ensureSemanticManagerInitialized();
if (semanticManager.canUseSemanticSearch) {
// Proceed only if a sufficient number of history entries have
// embeddings calculated.
return semanticManager.hasSufficientEntriesForSearching();
}
}
return false;
}
/**
* Starts a semantic search query.
*
* @param {object} queryContext
* The query context, including the search string.
* @param {Function} addCallback
* Callback to add results to the URL bar.
*/
async startQuery(queryContext, addCallback) {
let instance = this.queryInstance;
if (!this.#semanticManager) {
throw new Error(
"SemanticManager must be initialized via isActive() before calling startQuery()"
);
}
let resultObject = await this.#semanticManager.infer(queryContext);
this.#maybeRecordExposure();
let results = resultObject.results;
if (!results || instance != this.queryInstance) {
return;
}
for (let res of results) {
const result = new lazy.UrlbarResult(
UrlbarUtils.RESULT_TYPE.URL,
UrlbarUtils.RESULT_SOURCE.HISTORY,
...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
title: [res.title, UrlbarUtils.HIGHLIGHT.NONE],
url: [res.url, UrlbarUtils.HIGHLIGHT.NONE],
icon: UrlbarUtils.getIconForUrl(res.url),
isBlockable: true,
blockL10n: { id: "urlbar-result-menu-remove-from-history" },
helpUrl:
Services.urlFormatter.formatURLPref("app.support.baseURL") +
"awesome-bar-result-menu",
})
);
result.resultGroup = UrlbarUtils.RESULT_GROUP.HISTORY_SEMANTIC;
addCallback(this, result);
}
}
/**
* Records an exposure event for the semantic-history feature-gate, but
* **only once per profile**. Subsequent calls are ignored.
*/
#maybeRecordExposure() {
// Skip if we already recorded or if the gate is manually turned off.
if (this.#exposureRecorded) {
return;
}
// Look up our enrollment (experiment or rollout). If no slug, were not enrolled.
let metadata =
lazy.NimbusFeatures.urlbar.getEnrollmentMetadata(
lazy.EnrollmentType.EXPERIMENT
) ||
lazy.NimbusFeatures.urlbar.getEnrollmentMetadata(
lazy.EnrollmentType.ROLLOUT
);
if (!metadata?.slug) {
// Not part of any semantic-history experiment/rollout → nothing to record
return;
}
try {
// Actually send it once with the slug.
lazy.NimbusFeatures.urlbar.recordExposureEvent({
once: true,
slug: metadata.slug,
});
this.#exposureRecorded = true;
lazy.logger.debug(
`Nimbus exposure event sent (semanticHistory: ${metadata.slug}).`
);
} catch (ex) {
lazy.logger.warn("Unable to record semantic-history exposure event:", ex);
}
}
/**
* Gets the priority of this provider relative to other providers.
*
* @returns {number} The priority of this provider.
*/
getPriority() {
return 0;
}
onEngagement(queryContext, controller, details) {
let { result } = details;
if (details.selType == "dismiss") {
// Remove browsing history entries from Places.
lazy.PlacesUtils.history.remove(result.payload.url).catch(console.error);
controller.removeResult(result);
}
}
}
export var UrlbarProviderSemanticHistorySearch =
new ProviderSemanticHistorySearch();