mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 07:13:20 +00:00
Bug 1067899 - Add autocomplete result type for arbitrary URLs. r=mak
--HG-- extra : transplant_source : %B4%5C%83%26%B0%3EL%F1%B0%0E%B4%822y%E5%8B%0B%A4%01%3D
This commit is contained in:
parent
494c507329
commit
48ae4b40a5
@ -151,7 +151,8 @@
|
||||
|
||||
if (action) {
|
||||
switch (action.type) {
|
||||
case "switchtab": {
|
||||
case "switchtab": // Fall through.
|
||||
case "visiturl": {
|
||||
returnValue = action.params.url;
|
||||
break;
|
||||
}
|
||||
@ -327,6 +328,8 @@
|
||||
|
||||
url = submission.uri.spec;
|
||||
postData = submission.postData;
|
||||
} else if (action.type == "visiturl") {
|
||||
url = action.params.url;
|
||||
}
|
||||
}
|
||||
continueOperation.call(this);
|
||||
@ -1022,8 +1025,9 @@
|
||||
// TODO (bug 1054816): Centralise the implementation of actions
|
||||
// into a JS module.
|
||||
switch (action.type) {
|
||||
case "switchtab": //Fall through.
|
||||
case "keyword": {
|
||||
case "switchtab": // Fall through.
|
||||
case "keyword": // Fall through.
|
||||
case "visiturl": {
|
||||
url = action.params.url;
|
||||
break;
|
||||
}
|
||||
|
@ -1513,6 +1513,29 @@ this.PlacesUtils = {
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the favicon link url (moz-anno:) for a given page url.
|
||||
*
|
||||
* @param aPageURL url of the page to lookup the favicon for.
|
||||
* @resolves to the nsIURL of the favicon link
|
||||
* @rejects if the given url has no associated favicon.
|
||||
*/
|
||||
promiseFaviconLinkUrl: function (aPageUrl) {
|
||||
let deferred = Promise.defer();
|
||||
if (!(aPageUrl instanceof Ci.nsIURI))
|
||||
aPageUrl = NetUtil.newURI(aPageUrl);
|
||||
|
||||
PlacesUtils.favicons.getFaviconURLForPage(aPageUrl, uri => {
|
||||
if (uri) {
|
||||
uri = PlacesUtils.favicons.getFaviconLinkForIcon(uri);
|
||||
deferred.resolve(uri);
|
||||
} else {
|
||||
deferred.reject();
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the passed URL with a #-moz-resolution fragment
|
||||
* for the specified dimensions and devicePixelRatio.
|
||||
|
@ -711,7 +711,6 @@ Search.prototype = {
|
||||
// with an alias - which works like a keyword.
|
||||
hasFirstResult = yield this._matchSearchEngineAlias();
|
||||
}
|
||||
|
||||
let shouldAutofill = this._shouldAutofill;
|
||||
if (this.pending && !hasFirstResult && shouldAutofill) {
|
||||
// Or it may look like a URL we know about from search engines.
|
||||
@ -720,20 +719,35 @@ Search.prototype = {
|
||||
|
||||
if (this.pending && !hasFirstResult && shouldAutofill) {
|
||||
// It may also look like a URL we know from the database.
|
||||
// Here we can only try to predict whether the URL autofill query is
|
||||
// likely to return a result. If the prediction ends up being wrong,
|
||||
// later we will need to make up for the lack of a special first result.
|
||||
hasFirstResult = yield this._matchKnownUrl(conn, queries);
|
||||
}
|
||||
|
||||
if (this.pending && this._enableActions && !hasFirstResult) {
|
||||
// When all else fails, we search using the current search engine.
|
||||
yield this._matchCurrentSearchEngine();
|
||||
// If we don't have a result that matches what we know about, then
|
||||
// we use a fallback for things we don't know about.
|
||||
yield this._matchHeuristicFallback();
|
||||
}
|
||||
|
||||
// IMPORTANT: No other first result heuristics should run after
|
||||
// _matchHeuristicFallback().
|
||||
|
||||
yield this._sleep(Prefs.delay);
|
||||
if (!this.pending)
|
||||
return;
|
||||
|
||||
for (let [query, params] of queries) {
|
||||
yield conn.executeCached(query, params, this._onResultRow.bind(this));
|
||||
let hasResult = yield conn.executeCached(query, params, this._onResultRow.bind(this));
|
||||
|
||||
if (this.pending && params.query_type == QUERYTYPE_AUTOFILL_URL &&
|
||||
!hasResult) {
|
||||
// If we predicted that our URL autofill query might have gotten a
|
||||
// result, but it didn't, then we need to recover.
|
||||
yield this._matchHeuristicFallback();
|
||||
}
|
||||
|
||||
if (!this.pending)
|
||||
return;
|
||||
}
|
||||
@ -881,6 +895,70 @@ Search.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
// These are separated out so we can run them in two distinct cases:
|
||||
// (1) We didn't match on anything that we know about
|
||||
// (2) Our predictive query for URL autofill thought we may get a result,
|
||||
// but we didn't.
|
||||
_matchHeuristicFallback: function* () {
|
||||
// We may not have auto-filled, but this may still look like a URL.
|
||||
let hasFirstResult = yield this._matchUnknownUrl();
|
||||
// However, even if the input is a valid URL, we may not want to use
|
||||
// it as such. This can happen if the host would require whitelisting,
|
||||
// but isn't in the whitelist.
|
||||
|
||||
if (this.pending && !hasFirstResult) {
|
||||
// When all else fails, we search using the current search engine.
|
||||
yield this._matchCurrentSearchEngine();
|
||||
}
|
||||
},
|
||||
|
||||
// TODO (bug 1054814): Use visited URLs to inform which scheme to use, if the
|
||||
// scheme isn't specificed.
|
||||
_matchUnknownUrl: function* () {
|
||||
let flags = Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
|
||||
Ci.nsIURIFixup.FIXUP_FLAG_REQUIRE_WHITELISTED_HOST;
|
||||
let fixupInfo = null;
|
||||
try {
|
||||
fixupInfo = Services.uriFixup.getFixupURIInfo(this._originalSearchString,
|
||||
flags);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let uri = fixupInfo.preferredURI;
|
||||
// Check the host, as "http:///" is a valid nsIURI, but not useful to us.
|
||||
// But, some schemes are expected to have no host. So we check just against
|
||||
// schemes we know should have a host. This allows new schemes to be
|
||||
// implemented without us accidentally blocking access to them.
|
||||
let hostExpected = new Set(["http", "https", "ftp", "chrome", "resource"]);
|
||||
if (!uri || (hostExpected.has(uri.scheme) && !uri.host))
|
||||
return false;
|
||||
|
||||
let value = makeActionURL("visiturl", {
|
||||
url: uri.spec,
|
||||
input: this._originalSearchString,
|
||||
});
|
||||
|
||||
let match = {
|
||||
value: value,
|
||||
comment: uri.spec,
|
||||
style: "action visiturl",
|
||||
finalCompleteValue: this._originalSearchString,
|
||||
frecency: 0,
|
||||
};
|
||||
|
||||
try {
|
||||
let favicon = yield PlacesUtils.promiseFaviconLinkUrl(uri);
|
||||
if (favicon)
|
||||
match.icon = favicon.spec;
|
||||
} catch (e) {
|
||||
// It's possible we don't have a favicon for this - and that's ok.
|
||||
};
|
||||
|
||||
this._addMatch(match);
|
||||
return true;
|
||||
},
|
||||
|
||||
_onResultRow: function (row) {
|
||||
TelemetryStopwatch.finish(TELEMETRY_1ST_RESULT);
|
||||
let queryType = row.getResultByIndex(QUERYINDEX_QUERYTYPE);
|
||||
|
@ -27,7 +27,7 @@ add_task(function* test_tab_matches() {
|
||||
yield check_autocomplete({
|
||||
search: "abc.com",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "abc.com", searchQuery: "abc.com"}), title: "MozSearch" },
|
||||
matches: [ { uri: makeActionURI("visiturl", {url: "http://abc.com/", input: "abc.com"}), title: "http://abc.com/" },
|
||||
{ uri: makeActionURI("switchtab", {url: "http://abc.com/"}), title: "ABC rocks" } ]
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,53 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
|
||||
add_task(function*() {
|
||||
do_log_info("visit url, no protocol");
|
||||
yield check_autocomplete({
|
||||
search: "mozilla.org",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("visiturl", {url: "http://mozilla.org/", input: "mozilla.org"}), title: "http://mozilla.org/" }, ]
|
||||
});
|
||||
|
||||
do_log_info("visit url, with protocol");
|
||||
yield check_autocomplete({
|
||||
search: "https://mozilla.org",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("visiturl", {url: "https://mozilla.org/", input: "https://mozilla.org"}), title: "https://mozilla.org/" }, ]
|
||||
});
|
||||
|
||||
do_log_info("visit url, about: protocol (no host)");
|
||||
yield check_autocomplete({
|
||||
search: "about:config",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("visiturl", {url: "about:config", input: "about:config"}), title: "about:config" }, ]
|
||||
});
|
||||
|
||||
// This is distinct because of how we predict being able to url autofill via
|
||||
// host lookups.
|
||||
do_log_info("visit url, host matching visited host but not visited url");
|
||||
yield promiseAddVisits([
|
||||
{ uri: NetUtil.newURI("http://mozilla.org/wine/"), title: "Mozilla Wine", transition: TRANSITION_TYPED },
|
||||
]);
|
||||
yield check_autocomplete({
|
||||
search: "mozilla.org/rum",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("visiturl", {url: "http://mozilla.org/rum", input: "mozilla.org/rum"}), title: "http://mozilla.org/rum" }, ]
|
||||
});
|
||||
|
||||
// And hosts with no dot in them are special, due to requiring whitelisting.
|
||||
do_log_info("visit url, host matching visited host but not visited url, non-whitelisted host");
|
||||
Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
|
||||
"http://s.example.com/search");
|
||||
let engine = Services.search.getEngineByName("MozSearch");
|
||||
Services.search.currentEngine = engine;
|
||||
yield promiseAddVisits([
|
||||
{ uri: NetUtil.newURI("http://mozilla/bourbon/"), title: "Mozilla Bourbon", transition: TRANSITION_TYPED },
|
||||
]);
|
||||
yield check_autocomplete({
|
||||
search: "mozilla/rum",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "mozilla/rum", searchQuery: "mozilla/rum"}), title: "MozSearch" }, ]
|
||||
});
|
||||
});
|
@ -35,5 +35,6 @@ tail =
|
||||
[test_tabmatches.js]
|
||||
[test_trimming.js]
|
||||
[test_typed.js]
|
||||
[test_visiturl.js]
|
||||
[test_word_boundary_search.js]
|
||||
[test_zero_frecency.js]
|
||||
|
@ -1588,6 +1588,14 @@ extends="chrome://global/content/bindings/popup.xml#popup">
|
||||
// Remove the image, so we can style it ourselves with a generic
|
||||
// search icon.
|
||||
this.removeAttribute("image");
|
||||
} else if (action.type == "visiturl") {
|
||||
emphasiseUrl = false;
|
||||
url = action.params.url;
|
||||
|
||||
let sourceStr = this._stringBundle.GetStringFromName("visitURL");
|
||||
title = this._generateEmphasisPairs(sourceStr, [
|
||||
[trimURL(url), true],
|
||||
]);
|
||||
}
|
||||
|
||||
// Remove the "action" substring so that the correct style, if any,
|
||||
|
@ -119,12 +119,14 @@ treechildren.autocomplete-treebody::-moz-tree-cell-text(selected) {
|
||||
|
||||
.autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
|
||||
.autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
|
||||
.autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
|
||||
.autocomplete-richlistitem[type~="autofill"] .ac-url-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
|
||||
.autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
|
||||
.autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
|
||||
.autocomplete-richlistitem[type~="autofill"] .ac-title-box {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
@ -104,12 +104,14 @@ treechildren.autocomplete-treebody::-moz-tree-cell-text(selected) {
|
||||
|
||||
.autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
|
||||
.autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
|
||||
.autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
|
||||
.autocomplete-richlistitem[type~="autofill"] .ac-url-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
|
||||
.autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
|
||||
.autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
|
||||
.autocomplete-richlistitem[type~="autofill"] .ac-title-box {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
@ -132,12 +132,14 @@ treechildren.autocomplete-treebody::-moz-tree-cell-text(selected) {
|
||||
|
||||
.autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
|
||||
.autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
|
||||
.autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
|
||||
.autocomplete-richlistitem[type~="autofill"] .ac-url-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
|
||||
.autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
|
||||
.autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
|
||||
.autocomplete-richlistitem[type~="autofill"] .ac-title-box {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
Loading…
Reference in New Issue
Block a user