gecko-dev/browser/components/urlbar/UrlbarProviderInputHistory.jsm

207 lines
7.1 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/. */
"use strict";
/**
* This module exports a provider that offers input history (aka adaptive
* history) results. These results map typed search string to history results.
* That way, a user can find a particular history result again by typing the
* same string.
*/
var EXPORTED_SYMBOLS = ["UrlbarProviderInputHistory"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
UrlbarResult: "resource:///modules/UrlbarResult.jsm",
UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
});
// Sqlite result row index constants.
const QUERYINDEX = {
URL: 0,
TITLE: 1,
BOOKMARKED: 2,
BOOKMARKTITLE: 3,
TAGS: 4,
SWITCHTAB: 8,
};
// 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, ', ')
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, h.frecency
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
WHERE AUTOCOMPLETE_MATCH(NULL, h.url,
IFNULL(btitle, h.title), tags,
h.visit_count, h.typed, bookmarked,
t.open_count,
:matchBehavior, :searchBehavior)
ORDER BY rank DESC, h.frecency 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.
*/
get name() {
return "InputHistory";
}
/**
* The type of the provider, must be one of 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 UrlbarPrefs.get("suggest.history") && !queryContext.searchMode;
}
/**
* Starts querying.
* @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.
* @note Extended classes should return a Promise resolved when the provider
* is done searching AND returning results.
*/
async startQuery(queryContext, addCallback) {
let instance = this.queryInstance;
let conn = await 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 && UrlbarPrefs.get("suggest.openpage")) {
if (url == queryContext.currentPage) {
// Don't suggest switching to the current page.
continue;
}
let result = new UrlbarResult(
UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
UrlbarUtils.RESULT_SOURCE.TABS,
...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
url: [url, UrlbarUtils.HIGHLIGHT.TYPED],
title: [resultTitle, UrlbarUtils.HIGHLIGHT.TYPED],
icon: UrlbarUtils.getIconForUrl(url),
})
);
addCallback(this, result);
continue;
}
let resultSource = UrlbarUtils.RESULT_SOURCE.HISTORY;
if (bookmarked && UrlbarPrefs.get("suggest.bookmark")) {
resultSource = UrlbarUtils.RESULT_SOURCE.BOOKMARKS;
resultTitle = bookmarkTitle || historyTitle;
}
let resultTags = tags
.split(",")
.map(t => t.trim())
.filter(tag => {
let lowerCaseTag = tag.toLocaleLowerCase();
return queryContext.tokens.some(token =>
lowerCaseTag.includes(token.lowerCaseValue)
);
})
.sort();
let result = new UrlbarResult(
UrlbarUtils.RESULT_TYPE.URL,
resultSource,
...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
url: [url, UrlbarUtils.HIGHLIGHT.TYPED],
title: [resultTitle, UrlbarUtils.HIGHLIGHT.TYPED],
tags: [resultTags, UrlbarUtils.HIGHLIGHT.TYPED],
icon: UrlbarUtils.getIconForUrl(url),
})
);
addCallback(this, 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: PlacesUtils.tagsFolderId,
search_string: queryContext.searchString,
matchBehavior: Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE,
searchBehavior: UrlbarPrefs.get("defaultBehavior"),
userContextId: queryContext.userContextId,
maxResults: queryContext.maxResults,
},
];
}
}
var UrlbarProviderInputHistory = new ProviderInputHistory();