Bug 1648468 - Part 1 - Create UrlbarProviderAutofill.jsm. r=adw

Differential Revision: https://phabricator.services.mozilla.com/D82234
This commit is contained in:
Harry Twyford 2020-07-12 21:55:24 +00:00
Родитель 595dcb2420
Коммит 1868c41333
7 изменённых файлов: 823 добавлений и 557 удалений

Просмотреть файл

@ -48,6 +48,7 @@ const heuristicOrder = [
"UrlbarProviderSearchTips",
"Omnibox",
"UnifiedComplete",
"Autofill",
"HeuristicFallback",
];
/**
@ -74,8 +75,6 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
*
* @param {UrlbarQueryContext} context
* The query context.
* @returns {boolean} If results were successfully sorted. This return value
* is a stopgap and can be removed when bug 1648468 lands.
*/
sort(context) {
// This method is called multiple times per keystroke, so it should be as
@ -135,17 +134,10 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
UrlbarPrefs.get("maxHistoricalSearchSuggestions"),
context.maxResults
);
let hasUnifiedComplete = false;
// Do the first pass through the results. We only collect info for the
// second pass here.
for (let result of context.results) {
// Keep track of whether UnifiedComplete has returned results. We will
// quit early if it hasn't.
if (result.providerName == "UnifiedComplete") {
hasUnifiedComplete = true;
}
// The "Search in a Private Window" result should only be shown when there
// are other results and all of them are searches. It should not be shown
// if the user typed an alias because that's an explicit engine choice.
@ -186,16 +178,6 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
}
}
// Quit early if we're still waiting on UnifiedComplete. If it turns out
// UnifiedComplete doesn't need to return any results, it will call sort()
// regardless to unblock showing results.
if (
!hasUnifiedComplete &&
context.pendingHeuristicProviders.has("UnifiedComplete")
) {
return false;
}
// Do the second pass through results to build the list of unsorted results.
let unsortedResults = [];
for (let result of context.results) {
@ -325,7 +307,6 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
}
context.results = sortedResults;
return true;
}
/**

Просмотреть файл

@ -0,0 +1,755 @@
/* 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 provides an autofill result.
*/
var EXPORTED_SYMBOLS = ["UrlbarProviderAutofill"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
AboutPagesUtils: "resource://gre/modules/AboutPagesUtils.jsm",
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
UrlbarResult: "resource:///modules/UrlbarResult.jsm",
UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.jsm",
UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm",
UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
});
// Sqlite result row index constants.
const QUERYINDEX = {
QUERYTYPE: 0,
URL: 1,
TITLE: 2,
BOOKMARKED: 3,
BOOKMARKTITLE: 4,
TAGS: 5,
VISITCOUNT: 6,
TYPED: 7,
PLACEID: 8,
SWITCHTAB: 9,
FRECENCY: 10,
};
// Result row indexes for originQuery()
const QUERYINDEX_ORIGIN = {
AUTOFILLED_VALUE: 1,
URL: 2,
FRECENCY: 3,
};
// Result row indexes for urlQuery()
const QUERYINDEX_URL = {
URL: 1,
STRIPPED_URL: 2,
FRECENCY: 3,
};
// AutoComplete query type constants.
// Describes the various types of queries that we can process rows for.
const QUERYTYPE = {
FILTERED: 0,
AUTOFILL_ORIGIN: 1,
AUTOFILL_URL: 2,
ADAPTIVE: 3,
};
// `WITH` clause for the autofill queries. autofill_frecency_threshold.value is
// the mean of all moz_origins.frecency values + stddevMultiplier * one standard
// deviation. This is inlined directly in the SQL (as opposed to being a custom
// Sqlite function for example) in order to be as efficient as possible.
const SQL_AUTOFILL_WITH = `
WITH
frecency_stats(count, sum, squares) AS (
SELECT
CAST((SELECT IFNULL(value, 0.0) FROM moz_meta WHERE key = 'origin_frecency_count') AS REAL),
CAST((SELECT IFNULL(value, 0.0) FROM moz_meta WHERE key = 'origin_frecency_sum') AS REAL),
CAST((SELECT IFNULL(value, 0.0) FROM moz_meta WHERE key = 'origin_frecency_sum_of_squares') AS REAL)
),
autofill_frecency_threshold(value) AS (
SELECT
CASE count
WHEN 0 THEN 0.0
WHEN 1 THEN sum
ELSE (sum / count) + (:stddevMultiplier * sqrt((squares - ((sum * sum) / count)) / count))
END
FROM frecency_stats
)
`;
const SQL_AUTOFILL_FRECENCY_THRESHOLD = `host_frecency >= (
SELECT value FROM autofill_frecency_threshold
)`;
function originQuery({ select = "", where = "", having = "" }) {
return `${SQL_AUTOFILL_WITH}
SELECT :query_type,
fixed_up_host || '/',
IFNULL(:prefix, prefix) || moz_origins.host || '/',
frecency,
bookmarked,
id
FROM (
SELECT host,
host AS fixed_up_host,
TOTAL(frecency) AS host_frecency,
(
SELECT TOTAL(foreign_count) > 0
FROM moz_places
WHERE moz_places.origin_id = moz_origins.id
) AS bookmarked
${select}
FROM moz_origins
WHERE host BETWEEN :searchString AND :searchString || X'FFFF'
${where}
GROUP BY host
HAVING ${having}
UNION ALL
SELECT host,
fixup_url(host) AS fixed_up_host,
TOTAL(frecency) AS host_frecency,
(
SELECT TOTAL(foreign_count) > 0
FROM moz_places
WHERE moz_places.origin_id = moz_origins.id
) AS bookmarked
${select}
FROM moz_origins
WHERE host BETWEEN 'www.' || :searchString AND 'www.' || :searchString || X'FFFF'
${where}
GROUP BY host
HAVING ${having}
) AS grouped_hosts
JOIN moz_origins ON moz_origins.host = grouped_hosts.host
ORDER BY frecency DESC, id DESC
LIMIT 1 `;
}
function urlQuery(where1, where2) {
// We limit the search to places that are either bookmarked or have a frecency
// over some small, arbitrary threshold (20) in order to avoid scanning as few
// rows as possible. Keep in mind that we run this query every time the user
// types a key when the urlbar value looks like a URL with a path.
return `/* do not warn (bug no): cannot use an index to sort */
SELECT :query_type,
url,
:strippedURL,
frecency,
foreign_count > 0 AS bookmarked,
visit_count > 0 AS visited,
id
FROM moz_places
WHERE rev_host = :revHost
${where1}
UNION ALL
SELECT :query_type,
url,
:strippedURL,
frecency,
foreign_count > 0 AS bookmarked,
visit_count > 0 AS visited,
id
FROM moz_places
WHERE rev_host = :revHost || 'www.'
${where2}
ORDER BY frecency DESC, id DESC
LIMIT 1 `;
}
// Queries
const QUERY_ORIGIN_HISTORY_BOOKMARK = originQuery({
having: `bookmarked OR ${SQL_AUTOFILL_FRECENCY_THRESHOLD}`,
});
const QUERY_ORIGIN_PREFIX_HISTORY_BOOKMARK = originQuery({
where: `AND prefix BETWEEN :prefix AND :prefix || X'FFFF'`,
having: `bookmarked OR ${SQL_AUTOFILL_FRECENCY_THRESHOLD}`,
});
const QUERY_ORIGIN_HISTORY = originQuery({
select: `, (
SELECT TOTAL(visit_count) > 0
FROM moz_places
WHERE moz_places.origin_id = moz_origins.id
) AS visited`,
having: `visited AND ${SQL_AUTOFILL_FRECENCY_THRESHOLD}`,
});
const QUERY_ORIGIN_PREFIX_HISTORY = originQuery({
select: `, (
SELECT TOTAL(visit_count) > 0
FROM moz_places
WHERE moz_places.origin_id = moz_origins.id
) AS visited`,
where: `AND prefix BETWEEN :prefix AND :prefix || X'FFFF'`,
having: `visited AND ${SQL_AUTOFILL_FRECENCY_THRESHOLD}`,
});
const QUERY_ORIGIN_BOOKMARK = originQuery({
having: `bookmarked`,
});
const QUERY_ORIGIN_PREFIX_BOOKMARK = originQuery({
where: `AND prefix BETWEEN :prefix AND :prefix || X'FFFF'`,
having: `bookmarked`,
});
const QUERY_URL_HISTORY_BOOKMARK = urlQuery(
`AND (bookmarked OR frecency > 20)
AND strip_prefix_and_userinfo(url) BETWEEN :strippedURL AND :strippedURL || X'FFFF'`,
`AND (bookmarked OR frecency > 20)
AND strip_prefix_and_userinfo(url) BETWEEN 'www.' || :strippedURL AND 'www.' || :strippedURL || X'FFFF'`
);
const QUERY_URL_PREFIX_HISTORY_BOOKMARK = urlQuery(
`AND (bookmarked OR frecency > 20)
AND url BETWEEN :prefix || :strippedURL AND :prefix || :strippedURL || X'FFFF'`,
`AND (bookmarked OR frecency > 20)
AND url BETWEEN :prefix || 'www.' || :strippedURL AND :prefix || 'www.' || :strippedURL || X'FFFF'`
);
const QUERY_URL_HISTORY = urlQuery(
`AND (visited OR NOT bookmarked)
AND frecency > 20
AND strip_prefix_and_userinfo(url) BETWEEN :strippedURL AND :strippedURL || X'FFFF'`,
`AND (visited OR NOT bookmarked)
AND frecency > 20
AND strip_prefix_and_userinfo(url) BETWEEN 'www.' || :strippedURL AND 'www.' || :strippedURL || X'FFFF'`
);
const QUERY_URL_PREFIX_HISTORY = urlQuery(
`AND (visited OR NOT bookmarked)
AND frecency > 20
AND url BETWEEN :prefix || :strippedURL AND :prefix || :strippedURL || X'FFFF'`,
`AND (visited OR NOT bookmarked)
AND frecency > 20
AND url BETWEEN :prefix || 'www.' || :strippedURL AND :prefix || 'www.' || :strippedURL || X'FFFF'`
);
const QUERY_URL_BOOKMARK = urlQuery(
`AND bookmarked
AND strip_prefix_and_userinfo(url) BETWEEN :strippedURL AND :strippedURL || X'FFFF'`,
`AND bookmarked
AND strip_prefix_and_userinfo(url) BETWEEN 'www.' || :strippedURL AND 'www.' || :strippedURL || X'FFFF'`
);
const QUERY_URL_PREFIX_BOOKMARK = urlQuery(
`AND bookmarked
AND url BETWEEN :prefix || :strippedURL AND :prefix || :strippedURL || X'FFFF'`,
`AND bookmarked
AND url BETWEEN :prefix || 'www.' || :strippedURL AND :prefix || 'www.' || :strippedURL || X'FFFF'`
);
const kProtocolsWithIcons = [
"chrome:",
"moz-extension:",
"about:",
"http:",
"https:",
"ftp:",
];
function iconHelper(url) {
if (typeof url == "string") {
return kProtocolsWithIcons.some(p => url.startsWith(p))
? "page-icon:" + url
: UrlbarUtils.ICON.DEFAULT;
}
if (url && url instanceof URL && kProtocolsWithIcons.includes(url.protocol)) {
return "page-icon:" + url.href;
}
return UrlbarUtils.ICON.DEFAULT;
}
/**
* Class used to create the provider.
*/
class ProviderAutofill extends UrlbarProvider {
constructor() {
super();
// Maps the running queries by queryContext.
this.queries = new Map();
}
/**
* Returns the name of this provider.
* @returns {string} the name of this provider.
*/
get name() {
return "Autofill";
}
/**
* Returns the type of this provider.
* @returns {integer} one of the types from UrlbarUtils.PROVIDER_TYPE.*
*/
get type() {
return UrlbarUtils.PROVIDER_TYPE.HEURISTIC;
}
/**
* 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.
*/
async isActive(queryContext) {
// First of all, check for the autoFill pref.
if (!UrlbarPrefs.get("autoFill")) {
return false;
}
if (!queryContext.allowAutofill) {
return false;
}
if (queryContext.tokens.length != 1) {
return false;
}
// autoFill can only cope with history, bookmarks, and about: entries.
if (
!queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.HISTORY) &&
!queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.BOOKMARKS)
) {
return false;
}
// Autofill doesn't search tags or titles
if (
queryContext.tokens.some(
t =>
t.type == UrlbarTokenizer.TYPE.RESTRICT_TAG ||
t.type == UrlbarTokenizer.TYPE.RESTRICT_TITLE
)
) {
return false;
}
[this._strippedPrefix, this._searchString] = UrlbarUtils.stripURLPrefix(
queryContext.searchString
);
this._strippedPrefix = this._strippedPrefix.toLowerCase();
if (!this._searchString || !this._searchString.length) {
return false;
}
// Don't try to autofill if the search term includes any whitespace.
// This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH
// tokenizer ends up trimming the search string and returning a value
// that doesn't match it, or is even shorter.
if (UrlbarTokenizer.REGEXP_SPACES.test(queryContext.searchString)) {
return false;
}
// Fetch autofill result now, rather than in startQuery. We do this so the
// muxer doesn't have to wait on autofill for every query, since startQuery
// will be guaranteed to return a result very quickly using this approach.
// Bug 1651101 is filed to improve this behaviour.
let autofilled = await this._getAutofillResult(queryContext);
if (!autofilled || !this._autofillResult) {
return false;
}
return true;
}
/**
* Gets the provider's priority.
* @param {UrlbarQueryContext} queryContext The query context object
* @returns {number} The provider's priority for the given query.
*/
getPriority(queryContext) {
// Priority search results are restricting.
if (
this._autofillResult &&
this._autofillResult.type == UrlbarUtils.RESULT_TYPE.SEARCH
) {
return 1;
}
return 0;
}
/**
* Starts querying.
* @param {object} queryContext The query context object
* @param {function} addCallback Callback invoked by the provider to add a new
* result.
* @returns {Promise} resolved when the query stops.
*/
async startQuery(queryContext, addCallback) {
// This serves as the equivalent of checking this.queries.has to see if the
// query has been cancelled, since this._autofillResult is deleted in
// cancelQuery.
if (!this._autofillResult) {
return;
}
this._autofillResult.heuristic = true;
addCallback(this, this._autofillResult);
delete this._autofillResult;
}
/**
* Cancels a running query.
* @param {object} queryContext The query context object
*/
cancelQuery(queryContext) {
delete this._autofillResult;
}
/**
* Obtains the query to search for autofill origin results.
*
* @param {UrlbarQueryContext} queryContext
* @returns {array} consisting of the correctly optimized query to search the
* database with and an object containing the params to bound.
*/
_getOriginQuery(queryContext) {
// At this point, searchString is not a URL with a path; it does not
// contain a slash, except for possibly at the very end. If there is
// trailing slash, remove it when searching here to match the rest of the
// string because it may be an origin.
let searchStr = this._searchString.endsWith("/")
? this._searchString.slice(0, -1)
: this._searchString;
let opts = {
query_type: QUERYTYPE.AUTOFILL_ORIGIN,
searchString: searchStr.toLowerCase(),
stddevMultiplier: UrlbarPrefs.get("autoFill.stddevMultiplier"),
};
if (this._strippedPrefix) {
opts.prefix = this._strippedPrefix;
}
if (
queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.HISTORY) &&
queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.BOOKMARKS)
) {
return [
this._strippedPrefix
? QUERY_ORIGIN_PREFIX_HISTORY_BOOKMARK
: QUERY_ORIGIN_HISTORY_BOOKMARK,
opts,
];
}
if (queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.HISTORY)) {
return [
this._strippedPrefix
? QUERY_ORIGIN_PREFIX_HISTORY
: QUERY_ORIGIN_HISTORY,
opts,
];
}
if (queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.BOOKMARKS)) {
return [
this._strippedPrefix
? QUERY_ORIGIN_PREFIX_BOOKMARK
: QUERY_ORIGIN_BOOKMARK,
opts,
];
}
throw new Error("Either history or bookmark behavior expected");
}
/**
* Obtains the query to search for autoFill url results.
*
* @param {UrlbarQueryContext} queryContext
* @returns {array} consisting of the correctly optimized query to search the
* database with and an object containing the params to bound.
*/
_getUrlQuery(queryContext) {
// Try to get the host from the search string. The host is the part of the
// URL up to either the path slash, port colon, or query "?". If the search
// string doesn't look like it begins with a host, then return; it doesn't
// make sense to do a URL query with it.
const urlQueryHostRegexp = /^[^/:?]+/;
let hostMatch = urlQueryHostRegexp.exec(this._searchString);
if (!hostMatch) {
return [null, null];
}
let host = hostMatch[0].toLowerCase();
let revHost =
host
.split("")
.reverse()
.join("") + ".";
// Build a string that's the URL stripped of its prefix, i.e., the host plus
// everything after the host. Use queryContext.searchString instead of
// this._searchString because this._searchString has had unEscapeURIForUI()
// called on it. It's therefore not necessarily the literal URL.
let strippedURL = queryContext.searchString.trim();
if (this._strippedPrefix) {
strippedURL = strippedURL.substr(this._strippedPrefix.length);
}
strippedURL = host + strippedURL.substr(host.length);
let opts = {
query_type: QUERYTYPE.AUTOFILL_URL,
revHost,
strippedURL,
};
if (this._strippedPrefix) {
opts.prefix = this._strippedPrefix;
}
if (
queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.HISTORY) &&
queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.BOOKMARKS)
) {
return [
this._strippedPrefix
? QUERY_URL_PREFIX_HISTORY_BOOKMARK
: QUERY_URL_HISTORY_BOOKMARK,
opts,
];
}
if (queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.HISTORY)) {
return [
this._strippedPrefix ? QUERY_URL_PREFIX_HISTORY : QUERY_URL_HISTORY,
opts,
];
}
if (queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.BOOKMARKS)) {
return [
this._strippedPrefix ? QUERY_URL_PREFIX_BOOKMARK : QUERY_URL_BOOKMARK,
opts,
];
}
throw new Error("Either history or bookmark behavior expected");
}
/**
* Processes a matched row in the Places database and sets
* this._autofillResult to any matches.
* @param {object} row
* The matched row.
* @param {function} cancel
* A callback to cancel the search.
* @param {UrlbarQueryContext} queryContext
*/
_onResultRow(row, cancel, queryContext) {
let queryType = row.getResultByIndex(QUERYINDEX.QUERYTYPE);
let autofilledValue, finalCompleteValue;
switch (queryType) {
case QUERYTYPE.AUTOFILL_ORIGIN:
autofilledValue = row.getResultByIndex(
QUERYINDEX_ORIGIN.AUTOFILLED_VALUE
);
finalCompleteValue = row.getResultByIndex(QUERYINDEX_ORIGIN.URL);
break;
case QUERYTYPE.AUTOFILL_URL:
let url = row.getResultByIndex(QUERYINDEX_URL.URL);
let strippedURL = row.getResultByIndex(QUERYINDEX_URL.STRIPPED_URL);
// We autofill urls to-the-next-slash.
// http://mozilla.org/foo/bar/baz will be autofilled to:
// - http://mozilla.org/f[oo/]
// - http://mozilla.org/foo/b[ar/]
// - http://mozilla.org/foo/bar/b[az]
let strippedURLIndex = url.indexOf(strippedURL);
let strippedPrefix = url.substr(0, strippedURLIndex);
let nextSlashIndex = url.indexOf(
"/",
strippedURLIndex + strippedURL.length - 1
);
if (nextSlashIndex == -1) {
autofilledValue = url.substr(strippedURLIndex);
} else {
autofilledValue = url.substring(strippedURLIndex, nextSlashIndex + 1);
}
finalCompleteValue = strippedPrefix + autofilledValue;
break;
}
// We cancel the query right away since we're just looking for a single
// autofill result.
cancel();
let [title] = UrlbarUtils.stripPrefixAndTrim(finalCompleteValue, {
stripHttp: true,
trimEmptyQuery: true,
trimSlash: !this._searchString.includes("/"),
});
let result = new UrlbarResult(
UrlbarUtils.RESULT_TYPE.URL,
UrlbarUtils.RESULT_SOURCE.HISTORY,
...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
title: [title, UrlbarUtils.HIGHLIGHT.TYPED],
url: [finalCompleteValue, UrlbarUtils.HIGHLIGHT.TYPED],
icon: iconHelper(finalCompleteValue),
})
);
autofilledValue =
queryContext.searchString +
autofilledValue.substring(this._searchString.length);
result.autofill = {
value: autofilledValue,
selectionStart: queryContext.searchString.length,
selectionEnd: autofilledValue.length,
};
this._autofillResult = result;
}
async _getAutofillResult(queryContext) {
// We may be autofilling an about: link.
this._matchAboutPageForAutofill(queryContext);
if (this._autofillResult) {
return true;
}
// It may also look like a URL we know from the database.
await this._matchKnownUrl(queryContext);
if (this._autofillResult) {
return true;
}
// Or it may look like a search engine domain.
await this._matchSearchEngineDomain(queryContext);
if (this._autofillResult) {
return true;
}
return false;
}
_matchAboutPageForAutofill(queryContext) {
// Check that the typed query is at least one character longer than the
// about: prefix.
if (this._strippedPrefix != "about:" || !this._searchString) {
return;
}
for (const aboutUrl of AboutPagesUtils.visibleAboutUrls) {
if (aboutUrl.startsWith(`about:${this._searchString.toLowerCase()}`)) {
let [trimmedUrl] = UrlbarUtils.stripPrefixAndTrim(aboutUrl, {
stripHttp: true,
trimEmptyQuery: true,
trimSlash: !this._searchString.includes("/"),
});
let result = new UrlbarResult(
UrlbarUtils.RESULT_TYPE.URL,
UrlbarUtils.RESULT_SOURCE.HISTORY,
...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
title: [trimmedUrl, UrlbarUtils.HIGHLIGHT.TYPED],
url: [aboutUrl, UrlbarUtils.HIGHLIGHT.TYPED],
icon: iconHelper(aboutUrl),
})
);
let autofilledValue =
queryContext.searchString +
aboutUrl.substring(queryContext.searchString.length);
result.autofill = {
value: autofilledValue,
selectionStart: queryContext.searchString.length,
selectionEnd: autofilledValue.length,
};
this._autofillResult = result;
return;
}
}
}
async _matchKnownUrl(queryContext) {
let conn = await PlacesUtils.promiseLargeCacheDBConnection();
if (!conn) {
return;
}
// If search string looks like an origin, try to autofill against origins.
// Otherwise treat it as a possible URL. When the string has only one slash
// at the end, we still treat it as an URL.
let query, params;
if (
UrlbarTokenizer.looksLikeOrigin(this._searchString, {
ignoreKnownDomains: true,
})
) {
[query, params] = this._getOriginQuery(queryContext);
} else {
[query, params] = this._getUrlQuery(queryContext);
}
// _getrlQuery doesn't always return a query.
if (query) {
await conn.executeCached(query, params, (row, cancel) => {
this._onResultRow(row, cancel, queryContext);
});
}
}
async _matchSearchEngineDomain(queryContext) {
if (!UrlbarPrefs.get("autoFill.searchEngines")) {
return;
}
// engineForDomainPrefix only matches against engine domains.
// Remove an eventual trailing slash from the search string (without the
// prefix) and check if the resulting string is worth matching.
// Later, we'll verify that the found result matches the original
// searchString and eventually discard it.
let searchStr = this._searchString;
if (searchStr.indexOf("/") == searchStr.length - 1) {
searchStr = searchStr.slice(0, -1);
}
// If the search string looks more like a url than a domain, bail out.
if (
!UrlbarTokenizer.looksLikeOrigin(searchStr, { ignoreKnownDomains: true })
) {
return;
}
let engine = await UrlbarSearchUtils.engineForDomainPrefix(searchStr);
if (!engine) {
return;
}
let url = engine.searchForm;
let domain = engine.getResultDomain();
// Verify that the match we got is acceptable. Autofilling "example/" to
// "example.com/" would not be good.
if (
(this._strippedPrefix && !url.startsWith(this._strippedPrefix)) ||
!(domain + "/").includes(this._searchString)
) {
return;
}
// The value that's autofilled in the input is the prefix the user typed, if
// any, plus the portion of the engine domain that the user typed. Append a
// trailing slash too, as is usual with autofill.
let value =
this._strippedPrefix + domain.substr(domain.indexOf(searchStr)) + "/";
let result = new UrlbarResult(
UrlbarUtils.RESULT_TYPE.SEARCH,
UrlbarUtils.RESULT_SOURCE.SEARCH,
...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED],
icon: engine.iconURI ? engine.iconURI.spec : "",
})
);
let autofilledValue =
queryContext.searchString +
value.substring(queryContext.searchString.length);
result.autofill = {
value: autofilledValue,
selectionStart: queryContext.searchString.length,
selectionEnd: autofilledValue.length,
};
this._autofillResult = result;
}
}
var UrlbarProviderAutofill = new ProviderAutofill();

Просмотреть файл

@ -84,17 +84,8 @@ class ProviderUnifiedComplete extends UrlbarProvider {
acResult,
urls
);
// The Muxer gives UnifiedComplete special status and waits for it to
// return results before sorting them. We need to know that
// UnifiedComplete has finished even if it isn't returning results, so we
// call addCallback with an empty result here, which is something only
// UnifiedComplete is allowed to do.
if (!results.length) {
addCallback(this, null);
} else {
for (let result of results) {
addCallback(this, result);
}
for (let result of results) {
addCallback(this, result);
}
});
this.queries.delete(queryContext);

Просмотреть файл

@ -34,6 +34,7 @@ XPCOMUtils.defineLazyGetter(this, "logger", () =>
var localProviderModules = {
UrlbarProviderUnifiedComplete:
"resource:///modules/UrlbarProviderUnifiedComplete.jsm",
UrlbarProviderAutofill: "resource:///modules/UrlbarProviderAutofill.jsm",
UrlbarProviderHeuristicFallback:
"resource:///modules/UrlbarProviderHeuristicFallback.jsm",
UrlbarProviderInterventions:
@ -453,16 +454,9 @@ class Query {
return;
}
let addResult = true;
if (!result) {
addResult = false;
}
// Check if the result source should be filtered out. Pay attention to the
// heuristic result though, that is supposed to be added regardless.
if (
addResult &&
!this.acceptableSources.includes(result.source) &&
!result.heuristic &&
// Treat form history as searches for the purpose of acceptableSources.
@ -470,33 +464,21 @@ class Query {
result.source != UrlbarUtils.RESULT_SOURCE.HISTORY ||
!this.acceptableSources.includes(UrlbarUtils.RESULT_SOURCE.SEARCH))
) {
addResult = false;
return;
}
// Filter out javascript results for safety. The provider is supposed to do
// it, but we don't want to risk leaking these out.
if (
addResult &&
result.type != UrlbarUtils.RESULT_TYPE.KEYWORD &&
result.payload.url &&
result.payload.url.startsWith("javascript:") &&
!this.context.searchString.startsWith("javascript:") &&
UrlbarPrefs.get("filter.javascript")
) {
addResult = false;
}
// We wait on UnifiedComplete to return a heuristic result. If it has no
// heuristic result to return, we still need to sort and display results, so
// we follow the usual codepath to muxer.sort. We only offer this for
// UnifiedComplete, so we check the provider's name here. This is a stopgap
// measure until bug 1648468 lands.
if (!addResult) {
if (provider.name == "UnifiedComplete") {
this._notifyResultsFromProvider(provider);
}
return;
}
result.providerName = provider.name;
result.providerType = provider.type;
this.context.results.push(result);
@ -543,7 +525,7 @@ class Query {
}
_notifyResults() {
let sorted = this.muxer.sort(this.context);
this.muxer.sort(this.context);
if (this._heuristicProviderTimer) {
this._heuristicProviderTimer.cancel().catch(Cu.reportError);
@ -555,10 +537,6 @@ class Query {
this._chunkTimer = null;
}
if (!sorted) {
return;
}
// Before the muxer.sort call above, this.context.results should never be
// empty since this method is called when results are added. But the muxer
// may have excluded one or more results from the final sorted list. For

Просмотреть файл

@ -511,6 +511,58 @@ var UrlbarUtils = {
}
},
/**
* Strips parts of a URL defined in `options`.
*
* @param {string} spec
* The text to modify.
* @param {object} options
* @param {boolean} options.stripHttp
* Whether to strip http.
* @param {boolean} options.stripHttps
* Whether to strip https.
* @param {boolean} options.stripWww
* Whether to strip `www.`.
* @param {boolean} options.trimSlash
* Whether to trim the trailing slash.
* @param {boolean} options.trimEmptyQuery
* Whether to trim a trailing `?`.
* @param {boolean} options.trimEmptyHash
* Whether to trim a trailing `#`.
* @returns {array} [modified, prefix, suffix]
* modified: {string} The modified spec.
* prefix: {string} The parts stripped from the prefix, if any.
* suffix: {string} The parts trimmed from the suffix, if any.
*/
stripPrefixAndTrim(spec, options = {}) {
let prefix = "";
let suffix = "";
if (options.stripHttp && spec.startsWith("http://")) {
spec = spec.slice(7);
prefix = "http://";
} else if (options.stripHttps && spec.startsWith("https://")) {
spec = spec.slice(8);
prefix = "https://";
}
if (options.stripWww && spec.startsWith("www.")) {
spec = spec.slice(4);
prefix += "www.";
}
if (options.trimEmptyHash && spec.endsWith("#")) {
spec = spec.slice(0, -1);
suffix = "#" + suffix;
}
if (options.trimEmptyQuery && spec.endsWith("?")) {
spec = spec.slice(0, -1);
suffix = "?" + suffix;
}
if (options.trimSlash && spec.endsWith("/")) {
spec = spec.slice(0, -1);
suffix = "/" + suffix;
}
return [spec, prefix, suffix];
},
/**
* Used to filter out the javascript protocol from URIs, since we don't
* support LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL for those.

Просмотреть файл

@ -11,6 +11,7 @@ EXTRA_JS_MODULES += [
'UrlbarInput.jsm',
'UrlbarMuxerUnifiedComplete.jsm',
'UrlbarPrefs.jsm',
'UrlbarProviderAutofill.jsm',
'UrlbarProviderExtension.jsm',
'UrlbarProviderHeuristicFallback.jsm',
'UrlbarProviderInterventions.jsm',

Просмотреть файл

@ -14,8 +14,6 @@ const MS_PER_DAY = 86400000; // 24 * 60 * 60 * 1000
// AutoComplete query type constants.
// Describes the various types of queries that we can process rows for.
const QUERYTYPE_FILTERED = 0;
const QUERYTYPE_AUTOFILL_ORIGIN = 1;
const QUERYTYPE_AUTOFILL_URL = 2;
const QUERYTYPE_ADAPTIVE = 3;
// The default frecency value used when inserting matches with unknown frecency.
@ -133,201 +131,6 @@ const SQL_ADAPTIVE_QUERY = `/* do not warn (bug 487789) */
ORDER BY rank DESC, h.frecency DESC
LIMIT :maxResults`;
// Result row indexes for originQuery()
const QUERYINDEX_ORIGIN_AUTOFILLED_VALUE = 1;
const QUERYINDEX_ORIGIN_URL = 2;
const QUERYINDEX_ORIGIN_FRECENCY = 3;
// `WITH` clause for the autofill queries. autofill_frecency_threshold.value is
// the mean of all moz_origins.frecency values + stddevMultiplier * one standard
// deviation. This is inlined directly in the SQL (as opposed to being a custom
// Sqlite function for example) in order to be as efficient as possible.
const SQL_AUTOFILL_WITH = `
WITH
frecency_stats(count, sum, squares) AS (
SELECT
CAST((SELECT IFNULL(value, 0.0) FROM moz_meta WHERE key = 'origin_frecency_count') AS REAL),
CAST((SELECT IFNULL(value, 0.0) FROM moz_meta WHERE key = 'origin_frecency_sum') AS REAL),
CAST((SELECT IFNULL(value, 0.0) FROM moz_meta WHERE key = 'origin_frecency_sum_of_squares') AS REAL)
),
autofill_frecency_threshold(value) AS (
SELECT
CASE count
WHEN 0 THEN 0.0
WHEN 1 THEN sum
ELSE (sum / count) + (:stddevMultiplier * sqrt((squares - ((sum * sum) / count)) / count))
END
FROM frecency_stats
)
`;
const SQL_AUTOFILL_FRECENCY_THRESHOLD = `host_frecency >= (
SELECT value FROM autofill_frecency_threshold
)`;
function originQuery({ select = "", where = "", having = "" }) {
return `${SQL_AUTOFILL_WITH}
SELECT :query_type,
fixed_up_host || '/',
IFNULL(:prefix, prefix) || moz_origins.host || '/',
frecency,
bookmarked,
id
FROM (
SELECT host,
host AS fixed_up_host,
TOTAL(frecency) AS host_frecency,
(
SELECT TOTAL(foreign_count) > 0
FROM moz_places
WHERE moz_places.origin_id = moz_origins.id
) AS bookmarked
${select}
FROM moz_origins
WHERE host BETWEEN :searchString AND :searchString || X'FFFF'
${where}
GROUP BY host
HAVING ${having}
UNION ALL
SELECT host,
fixup_url(host) AS fixed_up_host,
TOTAL(frecency) AS host_frecency,
(
SELECT TOTAL(foreign_count) > 0
FROM moz_places
WHERE moz_places.origin_id = moz_origins.id
) AS bookmarked
${select}
FROM moz_origins
WHERE host BETWEEN 'www.' || :searchString AND 'www.' || :searchString || X'FFFF'
${where}
GROUP BY host
HAVING ${having}
) AS grouped_hosts
JOIN moz_origins ON moz_origins.host = grouped_hosts.host
ORDER BY frecency DESC, id DESC
LIMIT 1 `;
}
const QUERY_ORIGIN_HISTORY_BOOKMARK = originQuery({
having: `bookmarked OR ${SQL_AUTOFILL_FRECENCY_THRESHOLD}`,
});
const QUERY_ORIGIN_PREFIX_HISTORY_BOOKMARK = originQuery({
where: `AND prefix BETWEEN :prefix AND :prefix || X'FFFF'`,
having: `bookmarked OR ${SQL_AUTOFILL_FRECENCY_THRESHOLD}`,
});
const QUERY_ORIGIN_HISTORY = originQuery({
select: `, (
SELECT TOTAL(visit_count) > 0
FROM moz_places
WHERE moz_places.origin_id = moz_origins.id
) AS visited`,
having: `visited AND ${SQL_AUTOFILL_FRECENCY_THRESHOLD}`,
});
const QUERY_ORIGIN_PREFIX_HISTORY = originQuery({
select: `, (
SELECT TOTAL(visit_count) > 0
FROM moz_places
WHERE moz_places.origin_id = moz_origins.id
) AS visited`,
where: `AND prefix BETWEEN :prefix AND :prefix || X'FFFF'`,
having: `visited AND ${SQL_AUTOFILL_FRECENCY_THRESHOLD}`,
});
const QUERY_ORIGIN_BOOKMARK = originQuery({
having: `bookmarked`,
});
const QUERY_ORIGIN_PREFIX_BOOKMARK = originQuery({
where: `AND prefix BETWEEN :prefix AND :prefix || X'FFFF'`,
having: `bookmarked`,
});
// Result row indexes for urlQuery()
const QUERYINDEX_URL_URL = 1;
const QUERYINDEX_URL_STRIPPED_URL = 2;
const QUERYINDEX_URL_FRECENCY = 3;
function urlQuery(where1, where2) {
// We limit the search to places that are either bookmarked or have a frecency
// over some small, arbitrary threshold (20) in order to avoid scanning as few
// rows as possible. Keep in mind that we run this query every time the user
// types a key when the urlbar value looks like a URL with a path.
return `/* do not warn (bug no): cannot use an index to sort */
SELECT :query_type,
url,
:strippedURL,
frecency,
foreign_count > 0 AS bookmarked,
visit_count > 0 AS visited,
id
FROM moz_places
WHERE rev_host = :revHost
${where1}
UNION ALL
SELECT :query_type,
url,
:strippedURL,
frecency,
foreign_count > 0 AS bookmarked,
visit_count > 0 AS visited,
id
FROM moz_places
WHERE rev_host = :revHost || 'www.'
${where2}
ORDER BY frecency DESC, id DESC
LIMIT 1 `;
}
const QUERY_URL_HISTORY_BOOKMARK = urlQuery(
`AND (bookmarked OR frecency > 20)
AND strip_prefix_and_userinfo(url) BETWEEN :strippedURL AND :strippedURL || X'FFFF'`,
`AND (bookmarked OR frecency > 20)
AND strip_prefix_and_userinfo(url) BETWEEN 'www.' || :strippedURL AND 'www.' || :strippedURL || X'FFFF'`
);
const QUERY_URL_PREFIX_HISTORY_BOOKMARK = urlQuery(
`AND (bookmarked OR frecency > 20)
AND url BETWEEN :prefix || :strippedURL AND :prefix || :strippedURL || X'FFFF'`,
`AND (bookmarked OR frecency > 20)
AND url BETWEEN :prefix || 'www.' || :strippedURL AND :prefix || 'www.' || :strippedURL || X'FFFF'`
);
const QUERY_URL_HISTORY = urlQuery(
`AND (visited OR NOT bookmarked)
AND frecency > 20
AND strip_prefix_and_userinfo(url) BETWEEN :strippedURL AND :strippedURL || X'FFFF'`,
`AND (visited OR NOT bookmarked)
AND frecency > 20
AND strip_prefix_and_userinfo(url) BETWEEN 'www.' || :strippedURL AND 'www.' || :strippedURL || X'FFFF'`
);
const QUERY_URL_PREFIX_HISTORY = urlQuery(
`AND (visited OR NOT bookmarked)
AND frecency > 20
AND url BETWEEN :prefix || :strippedURL AND :prefix || :strippedURL || X'FFFF'`,
`AND (visited OR NOT bookmarked)
AND frecency > 20
AND url BETWEEN :prefix || 'www.' || :strippedURL AND :prefix || 'www.' || :strippedURL || X'FFFF'`
);
const QUERY_URL_BOOKMARK = urlQuery(
`AND bookmarked
AND strip_prefix_and_userinfo(url) BETWEEN :strippedURL AND :strippedURL || X'FFFF'`,
`AND bookmarked
AND strip_prefix_and_userinfo(url) BETWEEN 'www.' || :strippedURL AND 'www.' || :strippedURL || X'FFFF'`
);
const QUERY_URL_PREFIX_BOOKMARK = urlQuery(
`AND bookmarked
AND url BETWEEN :prefix || :strippedURL AND :prefix || :strippedURL || X'FFFF'`,
`AND bookmarked
AND url BETWEEN :prefix || 'www.' || :strippedURL AND :prefix || 'www.' || :strippedURL || X'FFFF'`
);
// Getters
const { XPCOMUtils } = ChromeUtils.import(
@ -959,20 +762,18 @@ Search.prototype = {
// For any given search, we run many queries/heuristics:
// 1) by alias (as defined in SearchService)
// 2) inline completion from search engine resultDomains
// 3) inline completion for origins (this._originQuery) or urls (this._urlQuery)
// 4) directly typed in url (ie, can be navigated to as-is)
// 5) submission for the current search engine
// 6) Places keywords
// 7) adaptive learning (this._adaptiveQuery)
// 8) open pages not supported by history (this._switchToTabQuery)
// 9) query based on match behavior
// 3) submission for the current search engine
// 4) Places keywords
// 5) adaptive learning (this._adaptiveQuery)
// 6) open pages not supported by history (this._switchToTabQuery)
// 7) query based on match behavior
//
// (6) only gets run if we get any filtered tokens, since if there are no
// (4) only gets run if we get any filtered tokens, since if there are no
// tokens, there is nothing to match.
//
// (1), (4), (5) only get run if actions are enabled. When actions are
// (1) and (5) only get run if actions are enabled. When actions are
// enabled, the first result is always a special result (resulting from one
// of the queries between (1) and (6) inclusive). As such, the UI is
// of the queries between (1) and (4) inclusive). As such, the UI is
// expected to auto-select the first result when actions are enabled. If the
// first result is an inline completion result, that will also be the
// default result and therefore be autofilled (this also happens if actions
@ -1127,20 +928,6 @@ Search.prototype = {
}
},
_matchAboutPageForAutofill() {
if (!this._shouldMatchAboutPages()) {
return false;
}
for (const url of AboutPagesUtils.visibleAboutUrls) {
if (url.startsWith(`about:${this._searchString.toLowerCase()}`)) {
this._result.setDefaultIndex(0);
this._addAutofillMatch(url.substr(6), url);
return true;
}
}
return false;
},
async _checkPreloadedSitesExpiry() {
if (!UrlbarPrefs.get("usepreloadedtopurls.enabled")) {
return;
@ -1289,30 +1076,6 @@ Search.prototype = {
let shouldAutofill = this._shouldAutofill;
if (this.pending && shouldAutofill) {
// It may also look like an about: link.
let matched = await this._matchAboutPageForAutofill();
if (matched) {
return true;
}
}
if (this.pending && shouldAutofill) {
// It may also look like a URL we know from the database.
let matched = await this._matchKnownUrl(conn);
if (matched) {
return true;
}
}
if (this.pending && shouldAutofill) {
// Or it may look like a search engine domain.
let matched = await this._matchSearchEngineDomain();
if (matched) {
return true;
}
}
if (this.pending && shouldAutofill) {
let matched = this._matchPreloadedSiteForAutofill();
if (matched) {
@ -1331,33 +1094,6 @@ Search.prototype = {
return false;
},
async _matchKnownUrl(conn) {
let gotResult = false;
// If search string looks like an origin, try to autofill against origins.
// Otherwise treat it as a possible URL. When the string has only one slash
// at the end, we still treat it as an URL.
let query, params;
if (
UrlbarTokenizer.looksLikeOrigin(this._searchString, {
ignoreKnownDomains: true,
})
) {
[query, params] = this._originQuery;
} else {
[query, params] = this._urlQuery;
}
// _urlQuery doesn't always return a query.
if (query) {
await conn.executeCached(query, params, (row, cancel) => {
gotResult = true;
this._onResultRow(row, cancel);
});
}
return gotResult;
},
async _matchPlacesKeyword(keyword) {
let entry = await PlacesUtils.keywords.fetch(keyword);
if (!entry) {
@ -1414,71 +1150,6 @@ Search.prototype = {
return true;
},
async _matchSearchEngineDomain() {
if (!UrlbarPrefs.get("autoFill.searchEngines")) {
return false;
}
if (!this._searchString) {
return false;
}
// engineForDomainPrefix only matches against engine domains.
// Remove an eventual trailing slash from the search string (without the
// prefix) and check if the resulting string is worth matching.
// Later, we'll verify that the found result matches the original
// searchString and eventually discard it.
let searchStr = this._searchString;
if (searchStr.indexOf("/") == searchStr.length - 1) {
searchStr = searchStr.slice(0, -1);
}
// If the search string looks more like a url than a domain, bail out.
if (
!UrlbarTokenizer.looksLikeOrigin(searchStr, { ignoreKnownDomains: true })
) {
return false;
}
let engine = await UrlbarSearchUtils.engineForDomainPrefix(searchStr);
if (!engine) {
return false;
}
let url = engine.searchForm;
let domain = engine.getResultDomain();
// Verify that the match we got is acceptable. Autofilling "example/" to
// "example.com/" would not be good.
if (
(this._strippedPrefix && !url.startsWith(this._strippedPrefix)) ||
!(domain + "/").includes(this._searchString)
) {
return false;
}
// The value that's autofilled in the input is the prefix the user typed, if
// any, plus the portion of the engine domain that the user typed. Append a
// trailing slash too, as is usual with autofill.
let value =
this._strippedPrefix + domain.substr(domain.indexOf(searchStr)) + "/";
let finalCompleteValue = url;
try {
let fixupInfo = Services.uriFixup.getFixupURIInfo(url, 0);
if (fixupInfo.fixedURI) {
finalCompleteValue = fixupInfo.fixedURI.spec;
}
} catch (ex) {}
this._result.setDefaultIndex(0);
this._addMatch({
value,
finalCompleteValue,
comment: engine.name,
icon: engine.iconURI ? engine.iconURI.spec : null,
style: "priority-search",
frecency: Infinity,
});
return true;
},
async _matchSearchEngineAlias(alias) {
let engine = await UrlbarSearchUtils.engineForAlias(alias);
if (!engine) {
@ -1600,14 +1271,6 @@ Search.prototype = {
_onResultRow(row, cancel) {
let queryType = row.getResultByIndex(QUERYINDEX_QUERYTYPE);
switch (queryType) {
case QUERYTYPE_AUTOFILL_ORIGIN:
this._result.setDefaultIndex(0);
this._addOriginAutofillMatch(row);
break;
case QUERYTYPE_AUTOFILL_URL:
this._result.setDefaultIndex(0);
this._addURLAutofillMatch(row);
break;
case QUERYTYPE_ADAPTIVE:
this._addAdaptiveQueryMatch(row);
break;
@ -1967,42 +1630,6 @@ Search.prototype = {
return { index, replace };
},
_addOriginAutofillMatch(row) {
this._addAutofillMatch(
row.getResultByIndex(QUERYINDEX_ORIGIN_AUTOFILLED_VALUE),
row.getResultByIndex(QUERYINDEX_ORIGIN_URL),
row.getResultByIndex(QUERYINDEX_ORIGIN_FRECENCY)
);
},
_addURLAutofillMatch(row) {
let url = row.getResultByIndex(QUERYINDEX_URL_URL);
let strippedURL = row.getResultByIndex(QUERYINDEX_URL_STRIPPED_URL);
// We autofill urls to-the-next-slash.
// http://mozilla.org/foo/bar/baz will be autofilled to:
// - http://mozilla.org/f[oo/]
// - http://mozilla.org/foo/b[ar/]
// - http://mozilla.org/foo/bar/b[az]
let value;
let strippedURLIndex = url.indexOf(strippedURL);
let strippedPrefix = url.substr(0, strippedURLIndex);
let nextSlashIndex = url.indexOf(
"/",
strippedURLIndex + strippedURL.length - 1
);
if (nextSlashIndex == -1) {
value = url.substr(strippedURLIndex);
} else {
value = url.substring(strippedURLIndex, nextSlashIndex + 1);
}
this._addAutofillMatch(
value,
strippedPrefix + value,
row.getResultByIndex(QUERYINDEX_URL_FRECENCY)
);
},
_addAutofillMatch(
autofilledValue,
finalCompleteValue,
@ -2273,125 +1900,6 @@ Search.prototype = {
return true;
},
/**
* Obtains the query to search for autofill origin results.
*
* @return an array consisting of the correctly optimized query to search the
* database with and an object containing the params to bound.
*/
get _originQuery() {
// At this point, _searchString is not a URL with a path; it does not
// contain a slash, except for possibly at the very end. If there is
// trailing slash, remove it when searching here to match the rest of the
// string because it may be an origin.
let searchStr = this._searchString.endsWith("/")
? this._searchString.slice(0, -1)
: this._searchString;
let opts = {
query_type: QUERYTYPE_AUTOFILL_ORIGIN,
searchString: searchStr.toLowerCase(),
stddevMultiplier: UrlbarPrefs.get("autoFill.stddevMultiplier"),
};
if (this._strippedPrefix) {
opts.prefix = this._strippedPrefix;
}
if (this.hasBehavior("history") && this.hasBehavior("bookmark")) {
return [
this._strippedPrefix
? QUERY_ORIGIN_PREFIX_HISTORY_BOOKMARK
: QUERY_ORIGIN_HISTORY_BOOKMARK,
opts,
];
}
if (this.hasBehavior("history")) {
return [
this._strippedPrefix
? QUERY_ORIGIN_PREFIX_HISTORY
: QUERY_ORIGIN_HISTORY,
opts,
];
}
if (this.hasBehavior("bookmark")) {
return [
this._strippedPrefix
? QUERY_ORIGIN_PREFIX_BOOKMARK
: QUERY_ORIGIN_BOOKMARK,
opts,
];
}
throw new Error("Either history or bookmark behavior expected");
},
/**
* Obtains the query to search for autoFill url results.
*
* @return an array consisting of the correctly optimized query to search the
* database with and an object containing the params to bound.
*/
get _urlQuery() {
// Try to get the host from the search string. The host is the part of the
// URL up to either the path slash, port colon, or query "?". If the search
// string doesn't look like it begins with a host, then return; it doesn't
// make sense to do a URL query with it.
if (!this._urlQueryHostRegexp) {
this._urlQueryHostRegexp = /^[^/:?]+/;
}
let hostMatch = this._urlQueryHostRegexp.exec(this._searchString);
if (!hostMatch) {
return [null, null];
}
let host = hostMatch[0].toLowerCase();
let revHost =
host
.split("")
.reverse()
.join("") + ".";
// Build a string that's the URL stripped of its prefix, i.e., the host plus
// everything after the host. Use _trimmedOriginalSearchString instead of
// this._searchString because this._searchString has had unEscapeURIForUI()
// called on it. It's therefore not necessarily the literal URL.
let strippedURL = this._trimmedOriginalSearchString;
if (this._strippedPrefix) {
strippedURL = strippedURL.substr(this._strippedPrefix.length);
}
strippedURL = host + strippedURL.substr(host.length);
let opts = {
query_type: QUERYTYPE_AUTOFILL_URL,
revHost,
strippedURL,
};
if (this._strippedPrefix) {
opts.prefix = this._strippedPrefix;
}
if (this.hasBehavior("history") && this.hasBehavior("bookmark")) {
return [
this._strippedPrefix
? QUERY_URL_PREFIX_HISTORY_BOOKMARK
: QUERY_URL_HISTORY_BOOKMARK,
opts,
];
}
if (this.hasBehavior("history")) {
return [
this._strippedPrefix ? QUERY_URL_PREFIX_HISTORY : QUERY_URL_HISTORY,
opts,
];
}
if (this.hasBehavior("bookmark")) {
return [
this._strippedPrefix ? QUERY_URL_PREFIX_BOOKMARK : QUERY_URL_BOOKMARK,
opts,
];
}
throw new Error("Either history or bookmark behavior expected");
},
// The result is notified to the search listener on a timer, to chunk multiple
// match updates together and avoid rebuilding the popup at every new match.
_notifyTimer: null,