summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs')
-rw-r--r--browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs267
1 files changed, 267 insertions, 0 deletions
diff --git a/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs b/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs
new file mode 100644
index 0000000000..c1f0cfb289
--- /dev/null
+++ b/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs
@@ -0,0 +1,267 @@
+/* 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 input history (aka adaptive
+ * history) results. These results map typed search strings to Urlbar results.
+ * That way, a user can find a particular result again by typing the same
+ * string.
+ */
+
+import {
+ UrlbarProvider,
+ UrlbarUtils,
+} from "resource:///modules/UrlbarUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
+ UrlbarProviderOpenTabs: "resource:///modules/UrlbarProviderOpenTabs.sys.mjs",
+ UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
+});
+
+// Sqlite result row index constants.
+const QUERYINDEX = {
+ URL: 0,
+ TITLE: 1,
+ BOOKMARKED: 2,
+ BOOKMARKTITLE: 3,
+ TAGS: 4,
+ SWITCHTAB: 8,
+};
+
+// Constants to support an alternative frecency algorithm.
+const PAGES_USE_ALT_FRECENCY = Services.prefs.getBoolPref(
+ "places.frecency.pages.alternative.featureGate",
+ false
+);
+const PAGES_FRECENCY_FIELD = PAGES_USE_ALT_FRECENCY
+ ? "alt_frecency"
+ : "frecency";
+
+// This SQL query fragment provides the following:
+// - whether the entry is bookmarked (QUERYINDEX_BOOKMARKED)
+// - the bookmark title, if it is a bookmark (QUERYINDEX_BOOKMARKTITLE)
+// - the tags associated with a bookmarked entry (QUERYINDEX_TAGS)
+const SQL_BOOKMARK_TAGS_FRAGMENT = `EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked,
+ ( SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL
+ ORDER BY lastModified DESC LIMIT 1
+ ) AS btitle,
+ ( SELECT GROUP_CONCAT(t.title ORDER BY t.title)
+ FROM moz_bookmarks b
+ JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent
+ WHERE b.fk = h.id
+ ) AS tags`;
+
+const SQL_ADAPTIVE_QUERY = `/* do not warn (bug 487789) */
+ SELECT h.url, h.title, ${SQL_BOOKMARK_TAGS_FRAGMENT}, h.visit_count,
+ h.typed, h.id, t.open_count, ${PAGES_FRECENCY_FIELD}
+ FROM (
+ SELECT ROUND(MAX(use_count) * (1 + (input = :search_string)), 1) AS rank,
+ place_id
+ FROM moz_inputhistory
+ WHERE input BETWEEN :search_string AND :search_string || X'FFFF'
+ GROUP BY place_id
+ ) AS i
+ JOIN moz_places h ON h.id = i.place_id
+ LEFT JOIN moz_openpages_temp t
+ ON t.url = h.url
+ AND (t.userContextId = :userContextId OR (t.userContextId <> -1 AND :userContextId IS NULL))
+ WHERE AUTOCOMPLETE_MATCH(NULL, h.url,
+ IFNULL(btitle, h.title), tags,
+ h.visit_count, h.typed, bookmarked,
+ t.open_count,
+ :matchBehavior, :searchBehavior,
+ NULL)
+ ORDER BY rank DESC, ${PAGES_FRECENCY_FIELD} DESC
+ LIMIT :maxResults`;
+
+/**
+ * Class used to create the provider.
+ */
+class ProviderInputHistory extends UrlbarProvider {
+ /**
+ * Unique name for the provider, used by the context to filter on providers.
+ *
+ * @returns {string}
+ */
+ get name() {
+ return "InputHistory";
+ }
+
+ /**
+ * The type of the provider, must be one of UrlbarUtils.PROVIDER_TYPE.
+ *
+ * @returns {UrlbarUtils.PROVIDER_TYPE}
+ */
+ get type() {
+ return UrlbarUtils.PROVIDER_TYPE.PROFILE;
+ }
+
+ /**
+ * 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.
+ */
+ isActive(queryContext) {
+ return (
+ (lazy.UrlbarPrefs.get("suggest.history") ||
+ lazy.UrlbarPrefs.get("suggest.bookmark") ||
+ lazy.UrlbarPrefs.get("suggest.openpage")) &&
+ !queryContext.searchMode
+ );
+ }
+
+ /**
+ * Starts querying. Extended classes should return a Promise resolved when the
+ * provider is done searching AND returning results.
+ *
+ * @param {UrlbarQueryContext} queryContext The query context object
+ * @param {Function} addCallback Callback invoked by the provider to add a new
+ * result. A UrlbarResult should be passed to it.
+ * @returns {Promise}
+ */
+ async startQuery(queryContext, addCallback) {
+ let instance = this.queryInstance;
+
+ let conn = await lazy.PlacesUtils.promiseLargeCacheDBConnection();
+ if (instance != this.queryInstance) {
+ return;
+ }
+
+ let [query, params] = this._getAdaptiveQuery(queryContext);
+ let rows = await conn.executeCached(query, params);
+ if (instance != this.queryInstance) {
+ return;
+ }
+
+ for (let row of rows) {
+ const url = row.getResultByIndex(QUERYINDEX.URL);
+ const openPageCount = row.getResultByIndex(QUERYINDEX.SWITCHTAB) || 0;
+ const historyTitle = row.getResultByIndex(QUERYINDEX.TITLE) || "";
+ const bookmarked = row.getResultByIndex(QUERYINDEX.BOOKMARKED);
+ const bookmarkTitle = bookmarked
+ ? row.getResultByIndex(QUERYINDEX.BOOKMARKTITLE)
+ : null;
+ const tags = row.getResultByIndex(QUERYINDEX.TAGS) || "";
+
+ let resultTitle = historyTitle;
+ if (openPageCount > 0 && lazy.UrlbarPrefs.get("suggest.openpage")) {
+ if (url == queryContext.currentPage) {
+ // Don't suggest switching to the current page.
+ continue;
+ }
+ let result = new lazy.UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
+ UrlbarUtils.RESULT_SOURCE.TABS,
+ ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
+ url: [url, UrlbarUtils.HIGHLIGHT.TYPED],
+ title: [resultTitle, UrlbarUtils.HIGHLIGHT.TYPED],
+ icon: UrlbarUtils.getIconForUrl(url),
+ userContextId: queryContext.userContextId || 0,
+ })
+ );
+ addCallback(this, result);
+ continue;
+ }
+
+ let resultSource;
+ if (bookmarked && lazy.UrlbarPrefs.get("suggest.bookmark")) {
+ resultSource = UrlbarUtils.RESULT_SOURCE.BOOKMARKS;
+ resultTitle = bookmarkTitle || historyTitle;
+ } else if (lazy.UrlbarPrefs.get("suggest.history")) {
+ resultSource = UrlbarUtils.RESULT_SOURCE.HISTORY;
+ } else {
+ continue;
+ }
+
+ let resultTags = tags.split(",").filter(tag => {
+ let lowerCaseTag = tag.toLocaleLowerCase();
+ return queryContext.tokens.some(token =>
+ lowerCaseTag.includes(token.lowerCaseValue)
+ );
+ });
+
+ let isBlockable = resultSource == UrlbarUtils.RESULT_SOURCE.HISTORY;
+
+ let result = new lazy.UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ resultSource,
+ ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
+ url: [url, UrlbarUtils.HIGHLIGHT.TYPED],
+ title: [resultTitle, UrlbarUtils.HIGHLIGHT.TYPED],
+ tags: [resultTags, UrlbarUtils.HIGHLIGHT.TYPED],
+ icon: UrlbarUtils.getIconForUrl(url),
+ isBlockable,
+ blockL10n: isBlockable
+ ? { id: "urlbar-result-menu-remove-from-history" }
+ : undefined,
+ helpUrl: isBlockable
+ ? Services.urlFormatter.formatURLPref("app.support.baseURL") +
+ "awesome-bar-result-menu"
+ : undefined,
+ })
+ );
+
+ addCallback(this, result);
+ }
+ }
+
+ onEngagement(state, queryContext, details, controller) {
+ let { result } = details;
+ if (result?.providerName != this.name) {
+ return;
+ }
+
+ if (
+ details.selType == "dismiss" &&
+ result.type == UrlbarUtils.RESULT_TYPE.URL
+ ) {
+ // Even if removing history normally also removes input history, that
+ // doesn't happen if the page is bookmarked, so we do remove input history
+ // regardless for this specific search term.
+ UrlbarUtils.removeInputHistory(
+ result.payload.url,
+ queryContext.searchString
+ ).catch(console.error);
+ // Remove browsing history for the page.
+ lazy.PlacesUtils.history.remove(result.payload.url).catch(console.error);
+ controller.removeResult(result);
+ }
+ }
+
+ /**
+ * Obtains the query to search for adaptive results.
+ *
+ * @param {UrlbarQueryContext} queryContext
+ * The current queryContext.
+ * @returns {Array} Contains the optimized query with which to search the
+ * database and an object containing the params to bound.
+ */
+ _getAdaptiveQuery(queryContext) {
+ return [
+ SQL_ADAPTIVE_QUERY,
+ {
+ parent: lazy.PlacesUtils.tagsFolderId,
+ search_string: queryContext.searchString.toLowerCase(),
+ matchBehavior: Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE,
+ searchBehavior: lazy.UrlbarPrefs.get("defaultBehavior"),
+ userContextId: lazy.UrlbarPrefs.get("switchTabs.searchAllContainers")
+ ? lazy.UrlbarProviderOpenTabs.getUserContextIdForOpenPagesTable(
+ null,
+ queryContext.isPrivate
+ )
+ : queryContext.userContextId,
+ maxResults: queryContext.maxResults,
+ },
+ ];
+ }
+}
+
+export var UrlbarProviderInputHistory = new ProviderInputHistory();