diff options
Diffstat (limited to 'browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs')
-rw-r--r-- | browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs | 267 |
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(); |